simics_sign/
lib.rs

1// Copyright (C) 2024 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4//! CdyLib signing tools for signing ELF and PE libraries to be loaded by Simics
5
6#![deny(missing_docs)]
7
8use chrono::Local;
9use object::{
10    elf::FileHeader64,
11    endian::LittleEndian,
12    read::{elf::ElfFile, pe::PeFile64},
13    Object, ObjectSection, ObjectSymbol,
14};
15use std::{
16    fs::{read, OpenOptions},
17    io::Write,
18    iter::once,
19    num::Wrapping,
20    path::{Path, PathBuf},
21};
22
23/// The symbol name containing the capabilities and signature of a module
24pub const MODULE_CAPABILITIES_SYMNAME: &str = "_module_capabilities_";
25/// The symbol name containing the date of the module
26pub const MODULE_DATE_SYMNAME: &str = "_module_date";
27/// The name of the text section for both Linux and Windows Gnu toolchains
28pub const TEXT_SECTION_NAME: &str = ".text";
29/// The name of the data section for both Linux and Windows Gnu toolchains
30pub const DATA_SECTION_NAME: &str = ".data";
31/// The maximum amount of data in a given section (text or data) to checksum
32pub const MAX_SECTION_CSUM_SIZE: usize = 256;
33// NOTE: Simics does not handle a username longer than 20 characters in its signing check and
34// may clobber the ELF if it sees a longer one. We won't allow that (20 chars + nul =
35// 21)
36/// The maximum length of a username used in a signature
37pub const SIMICS_SIGNATURE_UNAME_MAX_LEN: usize = 20;
38/// The minimum length of the signature field of the module capabilities symbol
39pub const SIMICS_SIGNATURE_MIN_LENGTH: usize = 44;
40
41type Elf<'data> = ElfFile<'data, FileHeader64<LittleEndian>>;
42
43#[derive(Debug, thiserror::Error)]
44/// An error type raised during singing
45pub enum Error {
46    #[error("File type of {path:?} not recognized. Must be PE64 or ELF64.")]
47    /// The file type of the input is not recognized and does not correctly parse as either
48    /// PE64 or ELF64.
49    FileTypeNotRecognized {
50        /// The path to the file that is not recognized
51        path: PathBuf,
52    },
53    #[error("_module_capabilities_ symbol missing")]
54    /// The _module_capabilities_ symbol could not be found in the file
55    ModuleCapabilitiesMissing,
56    #[error("Section not found for symbol {symbol} in {path:?}")]
57    /// A section containing a symbol with a given name could not be found in the file
58    SectionNotFound {
59        /// The symbol whose section could not be found
60        symbol: String,
61        /// The path which was missing the symbol
62        path: PathBuf,
63    },
64    #[error("_module_capabilities_ split sequence missing from symbol value")]
65    /// The sequence that splits the _module_capabilities_ symbol (usually '; ') was not found
66    SplitSequenceNotFound,
67    #[error("Section {section} not found in {path:?}")]
68    /// A section with a given name was not found
69    SectionMissing {
70        /// The name of the section that could not be found
71        section: String,
72        /// The path which was missing the symbol
73        path: PathBuf,
74    },
75    #[error("Signature unchanged after signing")]
76    /// The signature block was not modified by the signing process. This is a sanity check.
77    SignatureUnchanged,
78    #[error("Module unchanged after signing")]
79    /// The module was not modified by the signing process. This is a sanity check.
80    ModuleUnchanged,
81    #[error("File range for section {section} not found")]
82    /// No range of offsets in the file were found for a secion
83    SectionFileRangeMissing {
84        /// The name of the section whose offset range could not be determined
85        section: String,
86    },
87    #[error("Original and signed module lengths differ")]
88    /// The length of the original and signed modules differ. This is a sanity check.
89    ModuleLengthMismatch,
90    #[error("Missing terminating null byte in module capabilities")]
91    /// A null byte was not found in the _module_capabilities_ symbol
92    NullByteMissing,
93    #[error("Missing parent directory for path {path:?}")]
94    /// No parent directory for a path
95    MissingParentDirectory {
96        /// The path whose parent directory was not found
97        path: PathBuf,
98    },
99    #[error("Module is not signed.")]
100    /// The module was not signed
101    ModuleNotSigned,
102    #[error("Failed to open module output file {path:?}: {source}")]
103    /// An error occurred while opening the output file
104    OpenOutputFile {
105        /// The path to the output file that could not be opened
106        path: PathBuf,
107        /// The underlying error
108        source: std::io::Error,
109    },
110    #[error("Failed to set permissions for output file {path:?}: {source}")]
111    /// An error occurred while setting the permissions of the output file
112    SetPermissions {
113        /// The path to the output file that could not have its permissions set
114        path: PathBuf,
115        /// The underlying error
116        source: std::io::Error,
117    },
118    #[error("Failed to get metadata for output file {path:?}: {source}")]
119    /// An error occurred while getting the metadata of the output file
120    GetMetadata {
121        /// The path to the output file that could not have its metadata retrieved
122        path: PathBuf,
123        /// The underlying error
124        source: std::io::Error,
125    },
126    #[error("Failed to read directory {path:?}: {source}")]
127    /// An error occurred while reading a directory
128    ReadDirectory {
129        /// The path to the directory that could not be read
130        path: PathBuf,
131        /// The underlying error
132        source: std::io::Error,
133    },
134    #[error("Failed to write module output file to {path:?}: {source}")]
135    /// An error occurred while writing the output file
136    WriteOutputFile {
137        /// The path to the output file that could not be written
138        path: PathBuf,
139        /// The underlying error
140        source: std::io::Error,
141    },
142    #[error(transparent)]
143    /// A wrapped std::io::Error
144    IoError(#[from] std::io::Error),
145    #[error(transparent)]
146    /// A wrapped object::Error
147    ObjectError(#[from] object::Error),
148}
149
150type Result<T> = std::result::Result<T, Error>;
151
152/// A module for signing
153pub struct Sign {
154    module: PathBuf,
155    data: Vec<u8>,
156    signed: Vec<u8>,
157}
158
159impl Sign {
160    /// Start a new sign operation on a module located at a path
161    pub fn new<P>(module: P) -> Result<Self>
162    where
163        P: AsRef<Path>,
164    {
165        let data = read(module.as_ref())?;
166
167        let mut slf = Self {
168            module: module.as_ref().to_path_buf(),
169            data: data.clone(),
170            signed: Vec::new(),
171        };
172
173        let data = &data[..];
174
175        if let Ok(elf) = Elf::parse(data) {
176            slf.sign_elf(elf)?;
177            Ok(slf)
178        } else if let Ok(pe) = PeFile64::parse(data) {
179            slf.sign_pe(pe)?;
180            Ok(slf)
181        } else {
182            Err(Error::FileTypeNotRecognized {
183                path: slf.module.clone(),
184            })
185        }
186    }
187
188    fn sign_elf(&mut self, elf: Elf) -> Result<()> {
189        let module_capabilities = elf
190            .symbols()
191            .find(|s| s.name() == Ok(MODULE_CAPABILITIES_SYMNAME))
192            .ok_or_else(|| Error::ModuleCapabilitiesMissing)?;
193
194        let module_capabilities_data = &elf.data()[module_capabilities.address() as usize
195            ..module_capabilities.address() as usize + module_capabilities.size() as usize];
196
197        let signature = [b" ".repeat(43), b";\x00".to_vec()].concat();
198
199        let elf_data = elf.data().to_vec();
200
201        // Check if already signed -- ends with (" "*43);\x00
202        if !module_capabilities_data.ends_with(&signature) {
203            println!(
204                "Already signed with signature {:?}",
205                &module_capabilities_data[module_capabilities_data.len() - signature.len()..]
206            );
207            self.signed = elf_data;
208            // Already signed
209            return Ok(());
210        }
211
212        let split_seq = b"; ";
213
214        let signature_position = module_capabilities_data
215            .windows(split_seq.len())
216            .position(|w| w == split_seq)
217            .ok_or_else(|| Error::SplitSequenceNotFound)?
218            + split_seq.len();
219
220        let text_section =
221            elf.section_by_name(TEXT_SECTION_NAME)
222                .ok_or_else(|| Error::SectionMissing {
223                    section: TEXT_SECTION_NAME.to_string(),
224                    path: self.module.clone(),
225                })?;
226
227        let data_section =
228            elf.section_by_name(DATA_SECTION_NAME)
229                .ok_or_else(|| Error::SectionMissing {
230                    section: DATA_SECTION_NAME.to_string(),
231                    path: self.module.clone(),
232                })?;
233
234        // Checksum = 1 * (text_section.size * sum(text_section.data)) * (data_section.size * sum(data_section.data)) | 1
235        let csum: Wrapping<u32> = (Wrapping(1u32)
236            * (Wrapping(text_section.size() as u32)
237                * text_section
238                    .data()?
239                    .iter()
240                    .take(MAX_SECTION_CSUM_SIZE)
241                    .fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32)))
242            * (Wrapping(data_section.size() as u32)
243                * data_section
244                    .data()?
245                    .iter()
246                    .take(MAX_SECTION_CSUM_SIZE)
247                    .fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32))))
248            | Wrapping(1u32);
249
250        let uname = "simics"
251            .chars()
252            .take(SIMICS_SIGNATURE_UNAME_MAX_LEN)
253            .collect::<String>();
254
255        let datetime_string = Local::now().format("%Y-%M-%d %H:%M").to_string();
256
257        let mut signature = module_capabilities_data[..signature_position]
258            .iter()
259            .chain(once(&0u8))
260            .chain(&csum.0.to_le_bytes())
261            .chain(once(&0u8))
262            .chain(datetime_string.as_bytes())
263            .chain(once(&b';'))
264            .chain(uname.as_bytes())
265            .cloned()
266            .collect::<Vec<_>>();
267
268        signature.resize(
269            module_capabilities_data[..signature_position].len() + SIMICS_SIGNATURE_MIN_LENGTH,
270            0u8,
271        );
272
273        if signature == module_capabilities_data {
274            return Err(Error::SignatureUnchanged);
275        }
276
277        let pre_sig = elf_data[..module_capabilities.address() as usize].to_vec();
278
279        let post_sig = elf_data
280            [module_capabilities.address() as usize + module_capabilities.size() as usize..]
281            .to_vec();
282
283        self.signed = pre_sig
284            .iter()
285            .chain(signature.iter())
286            .chain(post_sig.iter())
287            .cloned()
288            .collect::<Vec<_>>();
289
290        if self.data.len() != self.signed.len() {
291            return Err(Error::ModuleLengthMismatch);
292        }
293
294        if self.data == self.signed {
295            return Err(Error::ModuleUnchanged);
296        }
297
298        Ok(())
299    }
300
301    fn sign_pe(&mut self, pe: PeFile64) -> Result<()> {
302        let module_capabilities = pe
303            .symbols()
304            .find(|s| s.name() == Ok(MODULE_CAPABILITIES_SYMNAME))
305            .ok_or_else(|| Error::ModuleCapabilitiesMissing)?;
306
307        let module_capabilities_section =
308            pe.section_by_index(module_capabilities.section().index().ok_or_else(|| {
309                Error::SectionNotFound {
310                    symbol: MODULE_CAPABILITIES_SYMNAME.to_string(),
311                    path: self.module.clone(),
312                }
313            })?)?;
314        let module_capabilities_offset = ((module_capabilities.address()
315            - module_capabilities_section.address())
316            + module_capabilities_section
317                .file_range()
318                .ok_or_else(|| Error::SectionFileRangeMissing {
319                    section: module_capabilities_section
320                        .name()
321                        .map(|n| n.to_string())
322                        .unwrap_or_else(|_| "unknown".to_string()),
323                })?
324                .0) as usize;
325
326        let module_capabilities_size = if module_capabilities.size() > 0 {
327            module_capabilities.size() as usize
328        } else {
329            &pe.data()[module_capabilities_offset..]
330                .iter()
331                .position(|b| *b == 0)
332                .ok_or_else(|| Error::NullByteMissing)?
333                + 1
334        };
335
336        let module_capabilities_data = &pe.data()
337            [module_capabilities_offset..module_capabilities_offset + module_capabilities_size];
338
339        let split_seq = b"; ";
340
341        let signature_position = module_capabilities_data
342            .windows(split_seq.len())
343            .position(|w| w == split_seq)
344            .ok_or_else(|| Error::SplitSequenceNotFound)?
345            + split_seq.len();
346
347        let text_section =
348            pe.section_by_name(TEXT_SECTION_NAME)
349                .ok_or_else(|| Error::SectionMissing {
350                    section: TEXT_SECTION_NAME.to_string(),
351                    path: self.module.clone(),
352                })?;
353
354        let data_section =
355            pe.section_by_name(DATA_SECTION_NAME)
356                .ok_or_else(|| Error::SectionMissing {
357                    section: DATA_SECTION_NAME.to_string(),
358                    path: self.module.clone(),
359                })?;
360
361        // Checksum = 1 * (text_section.size * sum(text_section.data)) * (data_section.size * sum(data_section.data)) | 1
362        let csum: Wrapping<u32> = (Wrapping(1u32)
363            * (Wrapping(text_section.size() as u32)
364                * text_section
365                    .data()?
366                    .iter()
367                    .take(MAX_SECTION_CSUM_SIZE)
368                    .fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32)))
369            * (Wrapping(data_section.size() as u32)
370                * data_section
371                    .data()?
372                    .iter()
373                    .take(MAX_SECTION_CSUM_SIZE)
374                    .fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32))))
375            | Wrapping(1u32);
376
377        let uname = "simics"
378            .chars()
379            .take(SIMICS_SIGNATURE_UNAME_MAX_LEN)
380            .collect::<String>();
381
382        let datetime_string = Local::now().format("%Y-%M-%d %H:%M").to_string();
383
384        let mut signature = module_capabilities_data[..signature_position]
385            .iter()
386            .chain(once(&0u8))
387            .chain(&csum.0.to_le_bytes())
388            .chain(once(&0u8))
389            .chain(datetime_string.as_bytes())
390            .chain(once(&b';'))
391            .chain(uname.as_bytes())
392            .cloned()
393            .collect::<Vec<_>>();
394
395        signature.resize(
396            module_capabilities_data[..signature_position].len() + SIMICS_SIGNATURE_MIN_LENGTH,
397            0u8,
398        );
399
400        if signature == module_capabilities_data {
401            return Err(Error::SignatureUnchanged);
402        }
403
404        let pe_data = pe.data().to_vec();
405        let pre_sig = pe_data[..module_capabilities_offset].to_vec();
406        let post_sig = pe_data[module_capabilities_offset + module_capabilities_size..].to_vec();
407        self.signed = pre_sig
408            .iter()
409            .chain(signature.iter())
410            .chain(post_sig.iter())
411            .cloned()
412            .collect::<Vec<_>>();
413
414        if self.data.len() != self.signed.len() {
415            return Err(Error::ModuleLengthMismatch);
416        }
417
418        if self.data == self.signed {
419            return Err(Error::ModuleUnchanged);
420        }
421
422        Ok(())
423    }
424
425    /// Write the signed file to the same directory as the input module as a specific name
426    pub fn write_as<S>(&mut self, name: S) -> Result<&mut Self>
427    where
428        S: AsRef<str>,
429    {
430        let output = self
431            .module
432            .parent()
433            .ok_or_else(|| Error::MissingParentDirectory {
434                path: self.module.clone(),
435            })?
436            .join(name.as_ref());
437        let mut file = OpenOptions::new()
438            .create(true)
439            .truncate(true)
440            .write(true)
441            .open(output)?;
442
443        #[cfg(unix)]
444        {
445            use std::os::unix::fs::PermissionsExt;
446            let mut perms = file.metadata()?.permissions();
447            perms.set_mode(0o755);
448            file.set_permissions(perms)?;
449        }
450
451        file.write_all(&self.signed)?;
452        Ok(self)
453    }
454
455    /// Write the signed file to an output path
456    pub fn write<P>(&mut self, output: P) -> Result<&mut Self>
457    where
458        P: AsRef<Path>,
459    {
460        let mut file = OpenOptions::new()
461            .create(true)
462            .truncate(true)
463            .write(true)
464            .open(output.as_ref())
465            .map_err(|e| Error::OpenOutputFile {
466                path: output.as_ref().to_path_buf(),
467                source: e,
468            })?;
469
470        #[cfg(unix)]
471        {
472            use std::os::unix::fs::PermissionsExt;
473            let mut perms = file
474                .metadata()
475                .map_err(|e| Error::GetMetadata {
476                    path: output.as_ref().to_path_buf(),
477                    source: e,
478                })?
479                .permissions();
480            perms.set_mode(0o755);
481            file.set_permissions(perms)
482                .map_err(|e| Error::SetPermissions {
483                    path: output.as_ref().to_path_buf(),
484                    source: e,
485                })?;
486        }
487
488        file.write_all(&self.signed)
489            .map_err(|e| Error::WriteOutputFile {
490                path: output.as_ref().to_path_buf(),
491                source: e,
492            })?;
493
494        file.flush()?;
495
496        if !output.as_ref().exists() {
497            return Err(Error::WriteOutputFile {
498                path: output.as_ref().to_path_buf(),
499                source: std::io::Error::from(std::io::ErrorKind::NotFound),
500            });
501        }
502
503        Ok(self)
504    }
505
506    /// Get the raw signed data
507    pub fn data(&self) -> Result<Vec<u8>> {
508        if self.signed.is_empty() {
509            Err(Error::ModuleNotSigned)
510        } else {
511            Ok(self.data.clone())
512        }
513    }
514}
515
516#[cfg(test)]
517mod test {
518    use super::Sign;
519    use std::{env::var, path::PathBuf};
520
521    #[cfg(debug_assertions)]
522    const TARGET_DIR: &str = "debug";
523
524    #[cfg(not(debug_assertions))]
525    const TARGET_DIR: &str = "release";
526
527    #[test]
528    #[cfg(windows)]
529    fn test_windows() {
530        let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
531        let workspace_dir = manifest_dir.parent().unwrap();
532        let hello_world = workspace_dir
533            .join("target")
534            .join(TARGET_DIR)
535            .join("hello_world.dll");
536        let _signed = Sign::new(hello_world).unwrap().data().unwrap();
537    }
538
539    #[test]
540    #[cfg(unix)]
541    fn test_linux() {
542        let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
543        let workspace_dir = manifest_dir.parent().unwrap();
544        let hello_world = workspace_dir
545            .join("target")
546            .join(TARGET_DIR)
547            .join("libhello_world.so");
548        println!("{:?}", hello_world);
549        let _signed = Sign::new(hello_world).unwrap().data().unwrap();
550    }
551}