1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
#![forbid(unsafe_code)]
//! Extracts the dependency tree information embedded in executables by
//! [`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable).
//!
//! This crate parses platform-specific binary formats ([ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format),
//! [PE](https://en.wikipedia.org/wiki/Portable_Executable),
//! [Mach-O](https://en.wikipedia.org/wiki/Mach-O), [WASM](https://en.wikipedia.org/wiki/WebAssembly)) and obtains the compressed audit data.
//!
//! Unlike other binary parsing crates, it is specifically designed to be resilient to malicious input.
//! It 100% safe Rust (including all dependencies) and performs no heap allocations.
//!
//! ## Usage
//!
//! **Note:** this is a low-level crate that only implements binary parsing. It rarely should be used directly.
//! You probably want the higher-level [`auditable-info`](https://docs.rs/auditable-info) crate instead.
//!
//! The following snippet demonstrates full extraction pipeline using this crate, including decompression
//! using the safe-Rust [`miniz_oxide`](http://docs.rs/miniz_oxide/) and optional JSON parsing
//! via [`auditable-serde`](http://docs.rs/auditable-serde/):
//!
//! ```rust,ignore
//! use std::io::{Read, BufReader};
//! use std::{error::Error, fs::File, str::FromStr};
//!
//! fn main() -> Result<(), Box<dyn Error>> {
//! // Read the input
//! let f = File::open("target/release/hello-world")?;
//! let mut f = BufReader::new(f);
//! let mut input_binary = Vec::new();
//! f.read_to_end(&mut input_binary)?;
//! // Extract the compressed audit data
//! let compressed_audit_data = auditable_extract::raw_auditable_data(&input_binary)?;
//! // Decompress it with your Zlib implementation of choice. We recommend miniz_oxide
//! use miniz_oxide::inflate::decompress_to_vec_zlib;
//! let decompressed_data = decompress_to_vec_zlib(&compressed_audit_data)
//! .map_err(|_| "Failed to decompress audit data")?;
//! let decompressed_data = String::from_utf8(decompressed_data)?;
//! println!("{}", decompressed_data);
//! // Parse the audit data to Rust data structures
//! let dependency_tree = auditable_serde::VersionInfo::from_str(&decompressed_data);
//! Ok(())
//! }
//! ```
//!
//! ## WebAssembly support
//!
//! We use a third-party crate [`wasmparser`](https://crates.io/crates/wasmparser)
//! created by Bytecode Alliance for parsing WebAssembly.
//! It is a robust and high-quality parser, but its dependencies contain some `unsafe` code,
//! most of which is not actually used in our build configuration.
//!
//! We have manually audited it and found it to be sound.
//! Still, the security guarantees for it are not as ironclad as for other parsers.
//! Because of that WebAssembly support is gated behind the optional `wasm` feature.
//! Be sure to [enable](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features)
//! the `wasm` feature if you want to parse WebAssembly.
#[cfg(feature = "wasm")]
mod wasm;
use binfarce::Format;
/// Extracts the Zlib-compressed dependency info from an executable.
///
/// This function does not allocate any memory on the heap and can be safely given untrusted input.
pub fn raw_auditable_data(data: &[u8]) -> Result<&[u8], Error> {
match binfarce::detect_format(data) {
Format::Elf32 { byte_order } => {
let section = binfarce::elf32::parse(data, byte_order)?
.section_with_name(".dep-v0")?
.ok_or(Error::NoAuditData)?;
Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?)
}
Format::Elf64 { byte_order } => {
let section = binfarce::elf64::parse(data, byte_order)?
.section_with_name(".dep-v0")?
.ok_or(Error::NoAuditData)?;
Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?)
}
Format::Macho => {
let parsed = binfarce::macho::parse(data)?;
let section = parsed.section_with_name("__DATA", ".dep-v0")?;
let section = section.ok_or(Error::NoAuditData)?;
Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?)
}
Format::PE => {
let parsed = binfarce::pe::parse(data)?;
let section = parsed
.section_with_name(".dep-v0")?
.ok_or(Error::NoAuditData)?;
Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?)
}
Format::Unknown => {
#[cfg(feature = "wasm")]
if data.starts_with(b"\0asm") {
return wasm::raw_auditable_data_wasm(data);
}
Err(Error::NotAnExecutable)
}
}
}
#[cfg(all(fuzzing, feature = "wasm"))]
pub fn raw_auditable_data_wasm_for_fuzz(input: &[u8]) -> Result<&[u8], Error> {
wasm::raw_auditable_data_wasm(input)
}
#[derive(Debug, Copy, Clone)]
pub enum Error {
NoAuditData,
NotAnExecutable,
UnexpectedEof,
MalformedFile,
SymbolsSectionIsMissing,
SectionIsMissing,
UnexpectedSectionType,
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = match self {
Error::NoAuditData => "No audit data found in the executable",
Error::NotAnExecutable => "Not an executable file",
Error::UnexpectedEof => "Unexpected end of file",
Error::MalformedFile => "Malformed executable file",
Error::SymbolsSectionIsMissing => "Symbols section missing from executable",
Error::SectionIsMissing => "Section is missing from executable",
Error::UnexpectedSectionType => "Unexpected executable section type",
};
write!(f, "{message}")
}
}
impl From<binfarce::ParseError> for Error {
fn from(e: binfarce::ParseError) -> Self {
match e {
binfarce::ParseError::MalformedInput => Error::MalformedFile,
binfarce::ParseError::UnexpectedEof => Error::UnexpectedEof,
binfarce::ParseError::SymbolsSectionIsMissing => Error::SymbolsSectionIsMissing,
binfarce::ParseError::SectionIsMissing(_) => Error::SectionIsMissing,
binfarce::ParseError::UnexpectedSectionType { .. } => Error::UnexpectedSectionType,
}
}
}