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,
        }
    }
}