Skip to content

Commit

Permalink
feat: expose unsafeness of pe module with authenticode
Browse files Browse the repository at this point in the history
  • Loading branch information
vthib committed Jan 8, 2023
1 parent ed8ef92 commit decde38
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 24 deletions.
6 changes: 6 additions & 0 deletions boreal-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ fn main() -> ExitCode {
let args = Args::parse();

let scanner = {
#[cfg(feature = "authenticode")]
// Safety: this is done before any multithreading context, so there is no risk of racing
// other calls into OpenSSL.
let mut compiler = unsafe { Compiler::new_with_pe_signatures() };
#[cfg(not(feature = "authenticode"))]
let mut compiler = Compiler::new();

if let Err(err) = compiler.add_rules_file(&args.rules_file) {
display_diagnostic(&args.rules_file, err);
return ExitCode::FAILURE;
Expand Down
3 changes: 3 additions & 0 deletions boreal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ yara = { version = "0.16", features = ["vendored"] }
[[bench]]
name = "benches"
harness = false

[package.metadata.docs.rs]
features = ["authenticode"]
61 changes: 58 additions & 3 deletions boreal/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,74 @@ struct ImportedModule {
impl Compiler {
/// Create a new object to compile YARA rules.
///
/// All available modules are enabled by default:
/// Almost available modules are enabled by default:
/// - `time`
/// - `math`
/// - `string`
/// - `hash` if the `hash` feature is enabled
/// - `elf`, `macho` and `pe` if the `object` feature is enabled
///
/// However, the pe module does not include signatures handling. To include it, you should have
/// the `authenticode` feature enabled, and use [`Compiler::new_with_pe_signatures`]
///
/// To create a compiler without some or all of those modules, use [`Compiler::default`] to
/// create a [`Compiler`] without any modules, then add back only the desired modules.
#[must_use]
pub fn new() -> Self {
let mut this = Self::new_without_pe_module();

#[cfg(feature = "object")]
let _r = this.add_module(crate::module::Pe::default());

this
}

/// Create a new object to compile YARA rules, including the pe module with signatures.
///
/// # Safety
///
/// The authenticode parsing requires creating OpenSSL objects, which is not thread-safe and
/// should be done while no other calls into OpenSSL can race with this call. Therefore,
/// this function should for example be called before setting up any multithreaded environment.
///
/// You can also directly create the Pe module early, and add it to a compiler later on.
///
/// ```ignore
/// // Safety: called before setting up multithreading context.
/// let mut compiler = unsafe { boreal::Compiler::new_with_pe_signatures() };
///
/// // Setup multithreading runtime
///
/// // Later on, in any thread:
/// compiler.add_rules("...");
///
/// // Or
///
/// // Safety: called before setting up multithreading context.
/// let pe_module = unsafe { boreal::module::Pe::new_with_signatures() };
///
/// // Setup multithreading runtime
///
/// // Later on, in any thread:
/// let mut compiler = boreal::Compiler::new_without_pe_module();
/// compiler.add_module(pe_module);
/// ```
#[cfg(all(feature = "object", feature = "authenticode"))]
#[must_use]
pub unsafe fn new_with_pe_signatures() -> Self {
let mut this = Self::new_without_pe_module();

let _r = this.add_module(crate::module::Pe::new_with_signatures());

this
}

/// Create a new object to compile YARA rules, without the pe module.
///
/// This is useful when needing to add the Pe module with signatures parsing enabled, see
/// [`crate::module::Pe::new_with_signatures`]
#[must_use]
pub fn new_without_pe_module() -> Self {
let mut this = Self::default();

let _r = this.add_module(crate::module::Time);
Expand All @@ -112,8 +169,6 @@ impl Compiler {
let _r = this.add_module(crate::module::Elf);
#[cfg(feature = "object")]
let _r = this.add_module(crate::module::MachO);
#[cfg(feature = "object")]
let _r = this.add_module(crate::module::Pe::new());

this
}
Expand Down
2 changes: 1 addition & 1 deletion boreal/src/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ mod tests {
{
test_type_traits_non_clonable(Elf);
test_type_traits_non_clonable(MachO);
test_type_traits_non_clonable(Pe::new());
test_type_traits_non_clonable(Pe::default());
}

assert_eq!(format!("{:?}", Value::Integer(0)), "Integer(0)");
Expand Down
43 changes: 25 additions & 18 deletions boreal/src/module/pe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ const MAX_EXPORT_NAME_LENGTH: usize = 512;
const MAX_RESOURCES: usize = 65536;

/// `pe` module. Allows inspecting PE inputs.
#[derive(Debug)]
#[derive(Copy, Clone, Default, Debug)]
pub struct Pe {
#[cfg(feature = "authenticode")]
token: authenticode_parser::InitializationToken,
token: Option<authenticode_parser::InitializationToken>,
}

#[repr(u8)]
Expand Down Expand Up @@ -1127,13 +1127,18 @@ impl ModuleData for Pe {
}

impl Pe {
/// Create a PE module.
/// Create a PE module with signatures parsing enabled.
///
/// # Safety
///
/// The authenticode parsing requires creating OpenSSL objects, which is not thread-safe and
/// should be done while no other calls into OpenSSL can race with this call. Therefore,
/// this function should for example be called before setting up any multithreaded environment.
#[must_use]
pub fn new() -> Self {
#[cfg(feature = "authenticode")]
pub unsafe fn new_with_signatures() -> Self {
Self {
// FIXME: expose this unsafeness to the user.
#[cfg(feature = "authenticode")]
token: unsafe { authenticode_parser::InitializationToken::new() },
token: Some(authenticode_parser::InitializationToken::new()),
}
}

Expand Down Expand Up @@ -1309,17 +1314,19 @@ impl Pe {
}

#[cfg(feature = "authenticode")]
if let Some((signatures, is_signed)) =
signatures::get_signatures(&data_dirs, mem, &self.token)
{
let _r = map.insert(
"number_of_signatures",
Value::Integer(signatures.len() as _),
);
let _r = map.insert("is_signed", Value::Integer(is_signed.into()));
let _r = map.insert("signatures", Value::Array(signatures));
} else {
let _r = map.insert("number_of_signatures", Value::Integer(0));
if let Some(token) = self.token.as_ref() {
if let Some((signatures, is_signed)) =
signatures::get_signatures(&data_dirs, mem, token)
{
let _r = map.insert(
"number_of_signatures",
Value::Integer(signatures.len() as _),
);
let _r = map.insert("is_signed", Value::Integer(is_signed.into()));
let _r = map.insert("signatures", Value::Array(signatures));
} else {
let _r = map.insert("number_of_signatures", Value::Integer(0));
}
}

Some(map)
Expand Down
31 changes: 29 additions & 2 deletions boreal/tests/it/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ impl Compiler {
Self::new_inner(false)
}

pub fn new_inner(with_yara: bool) -> Self {
let mut compiler = boreal::Compiler::new();
fn new_inner(with_yara: bool) -> Self {
let mut compiler = build_compiler();
compiler.add_module(super::module_tests::Tests);

let mut this = Self {
Expand Down Expand Up @@ -145,6 +145,33 @@ impl Compiler {
}
}

#[cfg(all(feature = "object", feature = "authenticode"))]
fn build_compiler() -> boreal::Compiler {
use boreal::module::Pe;
use std::sync::Mutex;

static PE_MODULE: Mutex<Option<Pe>> = Mutex::new(None);

let mut _guard = PE_MODULE.lock().unwrap();
let pe = _guard.get_or_insert_with(|| {
// Safety:
// - This is in a critical section that ensures a single thread can call this function
// - The only openssl code in this codebase is in the authenticode parsing, called when
// scanning. Since to scan rules must first be compiled, and this is called on the very
// first build of a compiler, there can be no other threads calling into OpenSSL while
// this is called.
unsafe { Pe::new_with_signatures() }
});
let mut compiler = boreal::Compiler::new_without_pe_module();
compiler.add_module(*pe);
compiler
}

#[cfg(not(all(feature = "object", feature = "authenticode")))]
fn build_compiler() -> boreal::Compiler {
boreal::Compiler::new()
}

impl Checker {
pub fn new(rule: &str) -> Self {
Self::new_inner(rule, true)
Expand Down

0 comments on commit decde38

Please sign in to comment.