Skip to main content

yara_x/scanner/
mod.rs

1/*! This module implements the YARA scanner.
2
3The scanner takes the rules produces by the compiler and scans data with them.
4*/
5use std::collections::{BTreeMap, HashMap};
6use std::fmt::{Debug, Formatter};
7use std::fs;
8use std::io::Read;
9use std::mem::transmute;
10use std::ops::Range;
11use std::path::{Path, PathBuf};
12use std::pin::Pin;
13use std::slice::Iter;
14use std::sync::Once;
15use std::sync::atomic::AtomicU64;
16use std::time::Duration;
17
18use bitvec::prelude::*;
19#[cfg(unix)]
20use memmap2::Advice;
21use memmap2::{Mmap, MmapOptions};
22use protobuf::{CodedInputStream, MessageDyn};
23use thiserror::Error;
24
25use crate::Variable;
26use crate::compiler::{RuleId, Rules};
27use crate::models::Rule;
28use crate::modules::{
29    ModuleContext, ModuleError, RegisteredModule, module_by_name,
30};
31pub(crate) use crate::scanner::context::RuntimeObject;
32pub(crate) use crate::scanner::context::RuntimeObjectHandle;
33pub(crate) use crate::scanner::context::ScanContext;
34pub(crate) use crate::scanner::context::ScanState;
35use crate::scanner::context::create_wasm_store_and_ctx;
36pub(crate) use crate::scanner::matches::Match;
37use crate::types::{Struct, TypeValue};
38use crate::variables::VariableError;
39use crate::wasm::MATCHING_RULES_BITMAP_BASE;
40use crate::wasm::runtime::Store;
41
42mod context;
43mod matches;
44
45pub mod blocks;
46
47#[cfg(test)]
48mod tests;
49
50/// Error returned when a scan operation fails.
51#[derive(Error, Debug)]
52#[non_exhaustive]
53pub enum ScanError {
54    /// The scan was aborted after the timeout period.
55    #[error("timeout")]
56    Timeout,
57    /// Could not open the scanned file.
58    #[error("can not open `{path}`: {err}")]
59    OpenError {
60        /// Path of the file being scanned.
61        path: PathBuf,
62        /// Error that occurred.
63        err: std::io::Error,
64    },
65    /// Could not map the scanned file into memory.
66    #[error("can not map `{path}`: {err}")]
67    MapError {
68        /// Path of the file being scanned.
69        path: PathBuf,
70        /// Error that occurred.
71        err: std::io::Error,
72    },
73    /// Could not deserialize the protobuf message for some YARA module.
74    #[error(
75        "can not deserialize protobuf message for YARA module `{module}`: {err}"
76    )]
77    ProtoError {
78        /// Module name.
79        module: String,
80        /// Error that occurred.
81        err: protobuf::Error,
82    },
83    /// The module is unknown.
84    #[error("unknown module `{module}`")]
85    UnknownModule {
86        /// Module name.
87        module: String,
88    },
89    /// Some module produced an error when it was invoked.
90    #[error("error in module `{module}`: {err}")]
91    ModuleError {
92        /// Module name.
93        module: String,
94        /// Error that occurred.
95        err: ModuleError,
96    },
97}
98
99/// Global counter that gets incremented every 1 second by a dedicated thread.
100///
101/// This counter is used for determining when a scan operation has timed out.
102static HEARTBEAT_COUNTER: AtomicU64 = AtomicU64::new(0);
103
104/// Used for spawning the thread that increments `HEARTBEAT_COUNTER`.
105static INIT_HEARTBEAT: Once = Once::new();
106
107/// Represents the data being scanned.
108///
109/// The scanned data can be backed by a slice owned by someone else, or a
110/// vector or memory-mapped file owned by `ScannedData` itself.
111pub enum ScannedData<'d> {
112    Slice(&'d [u8]),
113    Vec(Vec<u8>),
114    Mmap { mmap: Mmap, len: usize },
115}
116
117impl AsRef<[u8]> for ScannedData<'_> {
118    fn as_ref(&self) -> &[u8] {
119        match self {
120            ScannedData::Slice(s) => s,
121            ScannedData::Vec(v) => v.as_ref(),
122            ScannedData::Mmap { mmap, len } => &mmap.as_ref()[..*len],
123        }
124    }
125}
126
127impl ScannedData<'_> {
128    #[inline]
129    fn len(&self) -> usize {
130        self.as_ref().len()
131    }
132}
133
134impl<'d> TryInto<ScannedData<'d>> for &'d [u8] {
135    type Error = ScanError;
136    fn try_into(self) -> Result<ScannedData<'d>, Self::Error> {
137        Ok(ScannedData::Slice(self))
138    }
139}
140
141impl<'d, const N: usize> TryInto<ScannedData<'d>> for &'d [u8; N] {
142    type Error = ScanError;
143    fn try_into(self) -> Result<ScannedData<'d>, Self::Error> {
144        Ok(ScannedData::Slice(self))
145    }
146}
147
148/// Contains information about the time spent on a rule.
149#[cfg(feature = "rules-profiling")]
150pub struct ProfilingData<'r> {
151    /// Rule namespace.
152    pub namespace: &'r str,
153    /// Rule name.
154    pub rule: &'r str,
155    /// Time spent executing the rule's condition.
156    pub condition_exec_time: Duration,
157    /// Time spent matching the rule's patterns.
158    pub pattern_matching_time: Duration,
159}
160
161/// Optional information for the scan operation.
162#[derive(Debug, Default)]
163pub struct ScanOptions<'a> {
164    module_metadata: HashMap<&'a str, &'a [u8]>,
165}
166
167impl<'a> ScanOptions<'a> {
168    /// Creates a new instance of `ScanOptions` with no additional information
169    /// for the scan operation.
170    ///
171    /// Use other methods to add additional information.
172    pub fn new() -> Self {
173        Self { module_metadata: Default::default() }
174    }
175
176    /// Adds metadata for a YARA module.
177    pub fn set_module_metadata(
178        mut self,
179        module_name: &'a str,
180        metadata: &'a [u8],
181    ) -> Self {
182        self.module_metadata.insert(module_name, metadata);
183        self
184    }
185}
186
187/// Scans data with already compiled YARA rules.
188///
189/// The scanner receives a set of compiled [`Rules`] and scans data with those
190/// rules. The same scanner can be used for scanning multiple files or
191/// in-memory data sequentially, but you need multiple scanners for scanning in
192/// parallel.
193pub struct Scanner<'r> {
194    _rules: &'r Rules,
195    wasm_store: Pin<Box<Store<ScanContext<'static, 'static>>>>,
196    use_mmap: bool,
197    max_scan_size: Option<usize>,
198}
199
200impl<'r> Scanner<'r> {
201    /// Creates a new scanner.
202    pub fn new(rules: &'r Rules) -> Self {
203        let wasm_store = create_wasm_store_and_ctx(rules);
204        Self { _rules: rules, wasm_store, use_mmap: true, max_scan_size: None }
205    }
206
207    /// Sets a timeout for scan operations.
208    ///
209    /// The scan functions will return an [ScanError::Timeout] once the
210    /// provided timeout duration has elapsed. The scanner will make every
211    /// effort to stop promptly after the designated timeout duration. However,
212    /// in some cases, particularly with rules containing only a few patterns,
213    /// the scanner could potentially continue running for a longer period than
214    /// the specified timeout.
215    pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
216        self.scan_context_mut().set_timeout(timeout);
217        self
218    }
219
220    /// Sets the maximum number of matches per pattern.
221    ///
222    /// When some pattern reaches the maximum number of patterns it won't
223    /// produce more matches.
224    pub fn max_matches_per_pattern(&mut self, n: usize) -> &mut Self {
225        self.scan_context_mut()
226            .tracker
227            .pattern_matches
228            .max_matches_per_pattern(n);
229        self
230    }
231
232    /// Enables or disables fast scan mode.
233    ///
234    /// During rule compilation, the compiler analyzes rule conditions to
235    /// identify patterns that are only ever used in simple boolean existence
236    /// checks (e.g., `$a` in YARA). If a pattern is never queried for its match
237    /// count (`#a`), specific match offset (`@a`), match length (`!a`), or
238    /// evaluated inside a loop, it is classified as a fast-scan pattern.
239    ///
240    /// In fast scan mode, the scanner optimizes scans by stopping the search
241    /// and match tracking for these fast-scan patterns once their **very first
242    /// match** is found. Subsequent occurrences in the input data are ignored,
243    /// preventing redundant Aho-Corasick scans, regex evaluations, and match
244    /// memory allocations.
245    ///
246    /// Note that using fast scan mode implies that not all matches will be
247    /// reported. For instance, when iterating matches using [`ScanResults`],
248    /// you won't get all occurrences of the pattern in the file, only the first
249    /// one.
250    ///
251    /// ### Example
252    ///
253    /// ```
254    /// # use yara_x::{Compiler, Scanner};
255    /// let mut compiler = Compiler::new();
256    /// compiler.add_source(r#"
257    ///     rule test {
258    ///         strings:
259    ///             $a = "abc"
260    ///         condition:
261    ///             $a
262    ///     }
263    /// "#).unwrap();
264    ///
265    /// let rules = compiler.build();
266    /// let mut scanner = Scanner::new(&rules);
267    ///
268    /// // Enable fast scan mode.
269    /// scanner.fast_scan(true);
270    ///
271    /// // The haystack contains two matches of "abc".
272    /// let results = scanner.scan(b"abc...abc").unwrap();
273    ///
274    /// // Find the matching rule.
275    /// let matching_rule = results.matching_rules().next().unwrap();
276    ///
277    /// // Only a single match is returned for pattern $a.
278    /// let pattern = matching_rule.patterns().next().unwrap();
279    /// let mut matches = pattern.matches();
280    /// assert_eq!(matches.next().unwrap().range().start, 0); // The first match
281    /// assert!(matches.next().is_none()); // No other matches are returned
282    /// ```
283    pub fn fast_scan(&mut self, yes: bool) -> &mut Self {
284        self.scan_context_mut().tracker.fast_scan = yes;
285        self
286    }
287
288    /// Specifies whether [`Scanner::scan_file`] and [`Scanner::scan_file_with_options`]
289    /// may use memory-mapped files to read input.
290    ///
291    /// By default, the scanner uses memory mapping for very large files, as this
292    /// is typically faster than copying file contents into memory. However, this
293    /// approach has a drawback: if another process truncates the file during
294    /// scanning, a `SIGBUS` signal may occur.
295    ///
296    /// Setting this option disables memory mapping and forces the scanner to
297    /// always read files into an in-memory buffer instead. This method is slower,
298    /// but safer.
299    pub fn use_mmap(&mut self, yes: bool) -> &mut Self {
300        self.use_mmap = yes;
301        self
302    }
303
304    /// Sets the maximum size of the data that will be scanned.
305    ///
306    /// If the scanned data (either a file or an in-memory buffer) is larger
307    /// than this value, it will be truncated to the given size.
308    ///
309    /// The value returned by `filesize` will be also limited to the given
310    /// size.
311    ///
312    /// Also notice that some modules (pe, elf, macho, etc) may be unable
313    /// to properly parse truncated files.
314    pub fn max_scan_size(&mut self, size: usize) -> &mut Self {
315        self.max_scan_size = Some(size);
316        self
317    }
318
319    /// Sets a callback that is invoked every time a YARA rule calls the
320    /// `console` module.
321    ///
322    /// The `callback` function is invoked with a string representing the
323    /// message being logged. The function can print the message to stdout,
324    /// append it to a file, etc. If no callback is set these messages are
325    /// ignored.
326    pub fn console_log<F>(&mut self, callback: F) -> &mut Self
327    where
328        F: FnMut(String) + 'r,
329    {
330        self.scan_context_mut().console_log = Some(Box::new(callback));
331        self
332    }
333
334    /// Sets the context size for matches.
335    ///
336    /// This specifies how many bytes at the left and right of each match will
337    /// be reported by [`crate::Match::data_with_context`]. By default, the
338    /// match context size is 0, which means that [`crate::Match::data_with_context`]
339    /// will return exactly the same data as [`crate::Match::data`].
340    pub fn match_context_size(&mut self, size: usize) -> &mut Self {
341        self.scan_context_mut().match_context_size = size;
342        self
343    }
344
345    /// Scans in-memory data.
346    pub fn scan<'a>(
347        &'a mut self,
348        data: &'a [u8],
349    ) -> Result<ScanResults<'a, 'r>, ScanError> {
350        let mut data = data;
351        if let Some(max) = self.max_scan_size
352            && data.len() > max
353        {
354            data = &data[..max];
355        }
356        self.scan_impl(ScannedData::Slice(data), None)
357    }
358
359    /// Scans a file.
360    pub fn scan_file<'a, P>(
361        &'a mut self,
362        target: P,
363    ) -> Result<ScanResults<'a, 'r>, ScanError>
364    where
365        P: AsRef<Path>,
366    {
367        self.scan_impl(self.load_file(target.as_ref())?, None)
368    }
369
370    /// Like [`Scanner::scan`], but allows to specify additional scan options.
371    pub fn scan_with_options<'a, 'opts>(
372        &'a mut self,
373        data: &'a [u8],
374        options: ScanOptions<'opts>,
375    ) -> Result<ScanResults<'a, 'r>, ScanError> {
376        let mut data = data;
377        if let Some(max) = self.max_scan_size
378            && data.len() > max
379        {
380            data = &data[..max];
381        }
382        self.scan_impl(ScannedData::Slice(data), Some(options))
383    }
384
385    /// Like [`Scanner::scan_file`], but allows to specify additional scan
386    /// options.
387    pub fn scan_file_with_options<'opts, P>(
388        &mut self,
389        target: P,
390        options: ScanOptions<'opts>,
391    ) -> Result<ScanResults<'_, 'r>, ScanError>
392    where
393        P: AsRef<Path>,
394    {
395        self.scan_impl(self.load_file(target.as_ref())?, Some(options))
396    }
397
398    /// Sets the value of a global variable.
399    ///
400    /// The variable must has been previously defined by calling
401    /// [`crate::Compiler::define_global`], and the type it has during the
402    /// definition must match the type of the new value (`T`).
403    ///
404    /// The variable will retain the new value in subsequent scans, unless this
405    /// function is called again for setting a new value.
406    pub fn set_global<T: TryInto<Variable>>(
407        &mut self,
408        ident: &str,
409        value: T,
410    ) -> Result<&mut Self, VariableError>
411    where
412        VariableError: From<<T as TryInto<Variable>>::Error>,
413    {
414        self.scan_context_mut().set_global(ident, value)?;
415        Ok(self)
416    }
417
418    /// Sets the output data for a YARA module.
419    ///
420    /// Each YARA module generates an output consisting of a data structure that
421    /// contains information about the scanned file. This data structure is
422    /// represented by a Protocol Buffer message. Typically, you won't need to
423    /// provide this data yourself, as the YARA module automatically generates
424    /// different outputs for each file it scans.
425    ///
426    /// However, there are two scenarios in which you may want to provide the
427    /// output for a module yourself:
428    ///
429    /// 1) When the module does not produce any output on its own.
430    /// 2) When you already know the output of the module for the upcoming file
431    ///    to be scanned, and you prefer to reuse this data instead of generating
432    ///    it again.
433    ///
434    /// Case 1) applies to certain modules lacking a main function, thus
435    /// incapable of producing any output on their own. For such modules, you
436    /// must set the output before scanning the associated data. Since the
437    /// module's output typically varies with each scanned file, you need to
438    /// call [`Scanner::set_module_output`] prior to each invocation of
439    /// [`Scanner::scan`]. Once [`Scanner::scan`] is executed, the module's
440    /// output is consumed and will be empty unless set again before the
441    /// subsequent call.
442    ///
443    /// Case 2) applies when you have previously stored the module's output for
444    /// certain scanned data. In such cases, when rescanning the data, you can
445    /// utilize this function to supply the module's output, thereby preventing
446    /// redundant computation by the module. This optimization enhances
447    /// performance by eliminating the need for the module to reparse the
448    /// scanned data.
449    ///
450    /// <br>
451    ///
452    /// The `data` argument must be a Protocol Buffer message corresponding
453    /// to any of the existing YARA modules.
454    pub fn set_module_output(
455        &mut self,
456        data: Box<dyn MessageDyn>,
457    ) -> Result<&mut Self, ScanError> {
458        let descriptor = data.descriptor_dyn();
459        let full_name = descriptor.full_name();
460
461        // Check if the protobuf message passed to this function corresponds
462        // with any of the existing modules.
463        if !crate::modules::registered_modules()
464            .any(|m| m.root_descriptor().full_name() == full_name)
465        {
466            return Err(ScanError::UnknownModule {
467                module: full_name.to_string(),
468            });
469        }
470
471        self.scan_context_mut()
472            .user_provided_module_outputs
473            .insert(full_name.to_string(), data);
474
475        Ok(self)
476    }
477
478    /// Similar to [`Scanner::set_module_output`], but receives a module name
479    /// and the protobuf message as raw data.
480    ///
481    /// `name` can be either the YARA module name (i.e: "pe", "elf", "dotnet",
482    /// etc.) or the fully-qualified name for the protobuf message associated
483    /// to the module (i.e: "pe.PE", "elf.ELF", "dotnet.Dotnet", etc.).
484    pub fn set_module_output_raw(
485        &mut self,
486        name: &str,
487        data: &[u8],
488    ) -> Result<&mut Self, ScanError> {
489        // Try to find the module by name first, if not found, then try
490        // to find a module where the fully-qualified name for its protobuf
491        // message matches the `name` arguments.
492        let descriptor = module_by_name(name)
493            .map(|module| module.root_descriptor())
494            .or_else(|| {
495                crate::modules::registered_modules()
496                    .find(|module| {
497                        module.root_descriptor().full_name() == name
498                    })
499                    .map(|module| module.root_descriptor())
500            });
501
502        if descriptor.is_none() {
503            return Err(ScanError::UnknownModule { module: name.to_string() });
504        }
505
506        let mut is = CodedInputStream::from_bytes(data);
507
508        // Default recursion limit is 100, that's not enough for some deeply
509        // nested structures like the process tree in the `vt` module.
510        is.set_recursion_limit(500);
511
512        self.set_module_output(
513            descriptor.unwrap().parse_from(&mut is).map_err(|err| {
514                ScanError::ProtoError { module: name.to_string(), err }
515            })?,
516        )
517    }
518
519    /// Returns profiling data for the slowest N rules.
520    ///
521    /// The profiling data reflects the cumulative execution time of each rule
522    /// across all scanned files. This information is useful for identifying
523    /// performance bottlenecks. To reset the profiling data and start fresh
524    /// for subsequent scans, use [`Scanner::clear_profiling_data`].
525    #[cfg(feature = "rules-profiling")]
526    pub fn slowest_rules(&self, n: usize) -> Vec<ProfilingData<'_>> {
527        self.scan_context().slowest_rules(n)
528    }
529
530    /// Clears all accumulated profiling data.
531    ///
532    /// This method resets the profiling data collected during rule execution
533    /// across scanned files. Use this to start a new profiling session, ensuring
534    /// the results reflect only the data gathered after this method is called.
535    #[cfg(feature = "rules-profiling")]
536    pub fn clear_profiling_data(&mut self) {
537        self.scan_context_mut().clear_profiling_data()
538    }
539}
540
541impl<'r> Scanner<'r> {
542    #[cfg(feature = "rules-profiling")]
543    #[inline]
544    fn scan_context<'a>(&self) -> &ScanContext<'r, 'a> {
545        unsafe {
546            transmute::<&ScanContext<'static, 'static>, &ScanContext<'r, '_>>(
547                self.wasm_store.data(),
548            )
549        }
550    }
551    #[inline]
552    fn scan_context_mut<'a>(&mut self) -> &mut ScanContext<'r, 'a> {
553        unsafe {
554            transmute::<
555                &mut ScanContext<'static, 'static>,
556                &mut ScanContext<'r, '_>,
557            >(self.wasm_store.data_mut())
558        }
559    }
560
561    fn load_file<'a>(
562        &self,
563        path: &Path,
564    ) -> Result<ScannedData<'a>, ScanError> {
565        let file = fs::File::open(path).map_err(|err| {
566            ScanError::OpenError { path: path.to_path_buf(), err }
567        })?;
568
569        let mut size = file.metadata().map(|m| m.len()).unwrap_or(0) as usize;
570
571        if let Some(max_scan_size) = self.max_scan_size {
572            size = std::cmp::min(size, max_scan_size);
573        }
574
575        // For files smaller than ~500MB reading the whole file is faster than
576        // using a memory-mapped file.
577        let data = if self.use_mmap && size > 500_000_000 {
578            let mapped_file = unsafe {
579                MmapOptions::new().map_copy_read_only(&file).map_err(|err| {
580                    ScanError::MapError { path: path.to_path_buf(), err }
581                })
582            }?;
583            #[cfg(unix)]
584            mapped_file.advise(Advice::Sequential).map_err(|err| {
585                ScanError::MapError { path: path.to_path_buf(), err }
586            })?;
587            ScannedData::Mmap { mmap: mapped_file, len: size }
588        } else {
589            let mut buffered_file = Vec::with_capacity(size);
590            (&file)
591                .take(size as u64)
592                .read_to_end(&mut buffered_file)
593                .map_err(|err| ScanError::OpenError {
594                    path: path.to_path_buf(),
595                    err,
596                })?;
597            ScannedData::Vec(buffered_file)
598        };
599
600        Ok(data)
601    }
602
603    fn scan_impl<'a, 'opts>(
604        &'a mut self,
605        data: ScannedData<'a>,
606        options: Option<ScanOptions<'opts>>,
607    ) -> Result<ScanResults<'a, 'r>, ScanError> {
608        let ctx = self.scan_context_mut();
609
610        // Clear information about matches found in a previous scan, if any.
611        ctx.reset();
612
613        // Set the global variable `filesize` to the size of the scanned data.
614        ctx.set_filesize(data.len() as i64);
615
616        // Create the context that will be passed to the main function of each
617        // module.
618        let mut mod_ctx = ModuleContext::default();
619
620        // For each item in options.module_metadata, check if the module name
621        // is actually a registered module, and then add the corresponding
622        // metadata to mod_ctx.
623        for (module, meta) in options
624            .map(|options| options.module_metadata)
625            .into_iter()
626            .flatten()
627            .filter_map(|(name, meta)| Some((module_by_name(name)?, meta)))
628        {
629            mod_ctx.set_module_metadata(module.name(), meta);
630        }
631
632        // Indicate that the scanner is currently scanning the given data.
633        ctx.scan_state = ScanState::ScanningData(data);
634
635        let data = match &ctx.scan_state {
636            ScanState::ScanningData(data) => data.as_ref(),
637            _ => unreachable!(),
638        };
639
640        for module_name in ctx.compiled_rules.imports() {
641            // Look up the module in the module registry.
642            let module = module_by_name(module_name)
643                .unwrap_or_else(|| panic!("module `{module_name}` not found"));
644
645            let module_root_descriptor = module.root_descriptor();
646            let root_struct_name = module_root_descriptor.full_name();
647
648            let module_output;
649            // If the user already provided some output for the module by
650            // calling `Scanner::set_module_output`, use that output. If not,
651            // call the module's main function (if the module has a main
652            // function) for getting its output.
653            if let Some(output) =
654                ctx.user_provided_module_outputs.remove(root_struct_name)
655            {
656                module_output = Some(output);
657            } else {
658                if let Some(main_res) = module.main_fn(&mut mod_ctx, data) {
659                    module_output = Some(main_res.map_err(|err| {
660                        ScanError::ModuleError {
661                            module: module_name.to_string(),
662                            err,
663                        }
664                    })?);
665                } else {
666                    module_output = None;
667                }
668            }
669
670            if let Some(module_output) = &module_output {
671                // Make sure that the module is returning a protobuf message of
672                // the expected type.
673                debug_assert_eq!(
674                    module_output.descriptor_dyn().full_name(),
675                    root_struct_name,
676                    "main function of module `{}` must return `{}`, but returned `{}`",
677                    module_name,
678                    root_struct_name,
679                    module_output.descriptor_dyn().full_name(),
680                );
681
682                // Make sure that the module is returning a protobuf message
683                // where all required fields are initialized. This only applies
684                // to proto2, proto3 doesn't have "required" fields, all fields
685                // are optional.
686                debug_assert!(
687                    module_output.is_initialized_dyn(),
688                    "module `{}` returned a protobuf `{}` where some required fields are not initialized ",
689                    module_name,
690                    root_struct_name
691                );
692            }
693
694            // When constant folding is enabled we don't need to generate
695            // structure fields for enums. This is because during the
696            // optimization process symbols like MyEnum.ENUM_ITEM are resolved
697            // to their constant values at compile time. In other words, the
698            // compiler determines that MyEnum.ENUM_ITEM is equal to some value
699            // X, and uses that value in the generated code.
700            //
701            // However, without constant folding, enums are treated as any
702            // other field in a struct, and their values are determined at scan
703            // time. For that reason these fields must be generated for enums
704            // when constant folding is disabled.
705            let generate_fields_for_enums =
706                !cfg!(feature = "constant-folding");
707
708            let module_struct = Struct::from_proto_descriptor_and_msg(
709                &module_root_descriptor,
710                module_output.as_deref(),
711                generate_fields_for_enums,
712                false,
713            );
714
715            if let Some(module_output) = module_output {
716                ctx.module_outputs
717                    .insert(root_struct_name.to_string(), module_output);
718            }
719
720            // The data structure obtained from the module is added to the
721            // root structure. Any data from previous scans will be replaced
722            // with the new data structure.
723            ctx.root_struct
724                .add_field(module_name, TypeValue::Struct(module_struct));
725        }
726
727        // The user provided module outputs are not needed anymore. Let's
728        // clear any remaining entry in the hash map (which can happen if
729        // the user has set outputs for modules that are not even imported
730        // by the rules.
731        ctx.user_provided_module_outputs.clear();
732
733        // Clear the flag that indicates that the search phase was done.
734        ctx.set_pattern_search_done(false);
735
736        // Evaluate the conditions of every rule, this will call
737        // `ScanContext::search_for_patterns` if necessary.
738        ctx.eval_conditions()?;
739
740        let data = match ctx.scan_state.take() {
741            ScanState::ScanningData(data) => data,
742            _ => unreachable!(),
743        };
744
745        ctx.scan_state = ScanState::Finished(DataSnippets::SingleBlock(data));
746
747        Ok(ScanResults::new(ctx))
748    }
749}
750
751/// Helper type that exposes the data matched during a scan operation.
752///
753/// Matching data can be accessed through the [`Match::data`] method. Normally,
754/// this data can be retrieved by slicing directly into the scanned input.
755/// However, that requires the original input to remain valid until the scan
756/// results are processed. This works fine for a single contiguous block of
757/// memory, but is impractical when scanning multiple blocks, since holding
758/// onto all of them until the end would consume excessive memory.
759///
760/// To handle this, two strategies are used:
761///
762/// - **Single-block scans**: Data is accessed directly from the input slice.
763/// - **Multi-block scans**: Matching fragments are copied and retained in a
764///   BTreeMap until the results are processed. The keys in the btree are
765///   the offsets where the snippets start and the values are vectors with
766///   the snippet's data.
767///
768/// Each strategy corresponds to a variant in this enum.
769pub(crate) enum DataSnippets<'d> {
770    SingleBlock(ScannedData<'d>),
771    MultiBlock(BTreeMap<usize, Vec<u8>>),
772}
773
774impl DataSnippets<'_> {
775    pub(crate) fn get(&self, range: Range<usize>) -> Option<&[u8]> {
776        self.get_with_context(range, 0).map(|(data, _)| data)
777    }
778
779    /// Gets the data for the given `range`, but adding `context_size` additional
780    /// bytes to the left and right.
781    ///
782    /// Returns a tuple where the first item is the data slice with context,
783    /// and the second item is a range relative to the slice indicating where
784    /// the `range` part is located.
785    ///
786    /// The result will be `None` only if the data for `range` can't be found.
787    /// The additional bytes at the left and right will be added if possible,
788    /// but otherwise won't affect the result.
789    pub(crate) fn get_with_context(
790        &self,
791        range: Range<usize>,
792        context_size: usize,
793    ) -> Option<(&[u8], Range<usize>)> {
794        match self {
795            Self::SingleBlock(data) => {
796                let start = range.start.saturating_sub(context_size);
797                let end = range.end.saturating_add(context_size);
798                let end = std::cmp::min(end, data.len());
799
800                let slice = data.as_ref().get(start..end)?;
801                let rel_start = range.start - start;
802                let rel_end = range.end - start;
803
804                Some((slice, rel_start..rel_end))
805            }
806            Self::MultiBlock(btree) => {
807                for (snippet_offset, snippet_data) in
808                    btree.range(..=range.start).rev()
809                {
810                    // Calculate the start and end of the slice within the snippet.
811                    let start = range.start.saturating_sub(*snippet_offset);
812                    let end = range.end.saturating_sub(*snippet_offset);
813
814                    if end > snippet_data.len() {
815                        continue;
816                    }
817
818                    let start = start.saturating_sub(context_size);
819                    let end = end.saturating_add(context_size);
820                    let end = std::cmp::min(end, snippet_data.len());
821
822                    match snippet_data.get(start..end) {
823                        Some(data) if !data.is_empty() => {
824                            let rel_start =
825                                range.start - (*snippet_offset + start);
826                            let rel_end =
827                                range.end - (*snippet_offset + start);
828                            return Some((data, rel_start..rel_end));
829                        }
830                        _ => continue,
831                    }
832                }
833
834                None
835            }
836        }
837    }
838}
839
840/// Results of a scan operation.
841///
842/// Allows iterating over both the matching and non-matching rules.
843pub struct ScanResults<'a, 'r> {
844    ctx: &'a ScanContext<'r, 'a>,
845}
846
847impl Debug for ScanResults<'_, '_> {
848    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
849        f.write_str("ScanResults")
850    }
851}
852
853impl<'a, 'r> ScanResults<'a, 'r> {
854    fn new(ctx: &'a ScanContext<'r, 'a>) -> Self {
855        Self { ctx }
856    }
857
858    /// Returns an iterator that yields the matching rules in arbitrary order.
859    pub fn matching_rules(&self) -> MatchingRules<'_, 'r> {
860        MatchingRules::new(self.ctx)
861    }
862
863    /// Returns an iterator that yields the non-matching rules in arbitrary
864    /// order.
865    pub fn non_matching_rules(&self) -> NonMatchingRules<'_, 'r> {
866        NonMatchingRules::new(self.ctx)
867    }
868
869    /// Returns the protobuf produced by a YARA module after processing the
870    /// data.
871    ///
872    /// The result will be `None` if the module doesn't exist or didn't
873    /// produce any output.
874    pub fn module_output(
875        &self,
876        module_name: &str,
877    ) -> Option<&'a dyn MessageDyn> {
878        let module_descriptor =
879            module_by_name(module_name).map(|m| m.root_descriptor())?;
880        let module_output = self
881            .ctx
882            .module_outputs
883            .get(module_descriptor.full_name())?
884            .as_ref();
885        Some(module_output)
886    }
887
888    /// Returns an iterator that yields tuples composed of a YARA module name
889    /// and the protobuf produced by that module.
890    ///
891    /// Only returns the modules that produced some output.
892    pub fn module_outputs(&self) -> ModuleOutputs<'a, 'r> {
893        ModuleOutputs::new(self.ctx)
894    }
895}
896
897/// Iterator that yields the rules that matched during a scan.
898///
899/// Private rules are not included by default, use
900/// [`MatchingRules::include_private`] for changing this behaviour.
901pub struct MatchingRules<'a, 'r> {
902    ctx: &'a ScanContext<'r, 'a>,
903    iterator: Iter<'a, RuleId>,
904    len_non_private: usize,
905    len_private: usize,
906    include_private: bool,
907}
908
909impl<'a, 'r> MatchingRules<'a, 'r> {
910    fn new(ctx: &'a ScanContext<'r, 'a>) -> Self {
911        Self {
912            ctx,
913            iterator: ctx.matching_rules.iter(),
914            include_private: false,
915            len_non_private: ctx.matching_rules.len()
916                - ctx.num_matching_private_rules,
917            len_private: ctx.num_matching_private_rules,
918        }
919    }
920
921    /// Specifies whether the iterator should yield private rules.
922    ///
923    /// This does not reset the iterator to its initial state, the iterator will
924    /// continue from its current position.
925    pub fn include_private(mut self, yes: bool) -> Self {
926        self.include_private = yes;
927        self
928    }
929}
930
931impl<'a, 'r> Iterator for MatchingRules<'a, 'r> {
932    type Item = Rule<'a, 'r>;
933
934    fn next(&mut self) -> Option<Self::Item> {
935        let rules = self.ctx.compiled_rules;
936        loop {
937            let rule_id = *self.iterator.next()?;
938            let rule_info = rules.get(rule_id);
939            if rule_info.is_private {
940                self.len_private -= 1;
941            } else {
942                self.len_non_private -= 1;
943            }
944            if self.include_private || !rule_info.is_private {
945                return Some(Rule { ctx: Some(self.ctx), rule_info, rules });
946            }
947        }
948    }
949}
950
951impl ExactSizeIterator for MatchingRules<'_, '_> {
952    #[inline]
953    fn len(&self) -> usize {
954        if self.include_private {
955            self.len_non_private + self.len_private
956        } else {
957            self.len_non_private
958        }
959    }
960}
961
962/// Iterator that yields the rules that didn't match during a scan.
963///
964/// Private rules are not included by default, use
965/// [`NonMatchingRules::include_private`] for changing this behaviour.
966pub struct NonMatchingRules<'a, 'r> {
967    ctx: &'a ScanContext<'r, 'a>,
968    iterator: bitvec::slice::IterZeros<'a, u8, Lsb0>,
969    include_private: bool,
970    len_private: usize,
971    len_non_private: usize,
972}
973
974impl<'a, 'r> NonMatchingRules<'a, 'r> {
975    fn new(ctx: &'a ScanContext<'r, 'a>) -> Self {
976        let num_rules = ctx.compiled_rules.num_rules();
977        let main_memory = ctx
978            .wasm
979            .main_memory
980            .unwrap()
981            .data(unsafe { ctx.wasm.store.as_ref() });
982
983        let base = MATCHING_RULES_BITMAP_BASE as usize;
984
985        // Create a BitSlice that covers the region of main memory containing
986        // the bitmap that tells which rules matched and which did not.
987        let matching_rules_bitmap = BitSlice::<_, Lsb0>::from_slice(
988            &main_memory[base..base + num_rules / 8 + 1],
989        );
990
991        // The BitSlice will cover more bits than necessary, for example, if
992        // there are 3 rules the BitSlice will have 8 bits because it is
993        // created from a u8 slice that has 1 byte. Here we make sure that
994        // the BitSlice has exactly as many bits as existing rules.
995        let matching_rules_bitmap = &matching_rules_bitmap[0..num_rules];
996
997        Self {
998            ctx,
999            iterator: matching_rules_bitmap.iter_zeros(),
1000            include_private: false,
1001            len_non_private: ctx.compiled_rules.num_rules()
1002                - ctx.matching_rules.len()
1003                - ctx.num_non_matching_private_rules,
1004            len_private: ctx.num_non_matching_private_rules,
1005        }
1006    }
1007
1008    /// Specifies whether the iterator should yield private rules.
1009    ///
1010    /// This does not reset the iterator to its initial state, the iterator will
1011    /// continue from its current position.
1012    pub fn include_private(mut self, yes: bool) -> Self {
1013        self.include_private = yes;
1014        self
1015    }
1016}
1017
1018impl<'a, 'r> Iterator for NonMatchingRules<'a, 'r> {
1019    type Item = Rule<'a, 'r>;
1020
1021    fn next(&mut self) -> Option<Self::Item> {
1022        let rules = self.ctx.compiled_rules;
1023
1024        loop {
1025            let rule_id = RuleId::from(self.iterator.next()?);
1026            let rule_info = rules.get(rule_id);
1027
1028            if rule_info.is_private {
1029                self.len_private -= 1;
1030            } else {
1031                self.len_non_private -= 1;
1032            }
1033
1034            if self.include_private || !rule_info.is_private {
1035                return Some(Rule { ctx: Some(self.ctx), rule_info, rules });
1036            }
1037        }
1038    }
1039}
1040
1041impl ExactSizeIterator for NonMatchingRules<'_, '_> {
1042    #[inline]
1043    fn len(&self) -> usize {
1044        if self.include_private {
1045            self.len_non_private + self.len_private
1046        } else {
1047            self.len_non_private
1048        }
1049    }
1050}
1051
1052/// Iterator that returns the outputs produced by YARA modules.
1053pub struct ModuleOutputs<'a, 'r> {
1054    ctx: &'a ScanContext<'r, 'a>,
1055    len: usize,
1056    iterator: Box<dyn Iterator<Item = &'static dyn RegisteredModule> + 'a>,
1057}
1058
1059impl<'a, 'r> ModuleOutputs<'a, 'r> {
1060    fn new(ctx: &'a ScanContext<'r, 'a>) -> Self {
1061        Self {
1062            ctx,
1063            len: ctx.module_outputs.len(),
1064            iterator: Box::new(crate::modules::registered_modules()),
1065        }
1066    }
1067}
1068
1069impl ExactSizeIterator for ModuleOutputs<'_, '_> {
1070    #[inline]
1071    fn len(&self) -> usize {
1072        self.len
1073    }
1074}
1075
1076impl<'a> Iterator for ModuleOutputs<'a, '_> {
1077    type Item = (&'a str, &'a dyn MessageDyn);
1078
1079    fn next(&mut self) -> Option<Self::Item> {
1080        loop {
1081            let module = self.iterator.next()?;
1082            if let Some(module_output) = self
1083                .ctx
1084                .module_outputs
1085                .get(module.root_descriptor().full_name())
1086            {
1087                return Some((module.name(), module_output.as_ref()));
1088            }
1089        }
1090    }
1091}
1092
1093#[cfg(test)]
1094mod snippet_tests {
1095    use super::DataSnippets;
1096    use std::collections::BTreeMap;
1097
1098    #[test]
1099    fn snippets_multiblock() {
1100        let mut btree_map = BTreeMap::new();
1101
1102        btree_map.insert(0, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1103        btree_map.insert(50, vec![50, 51, 52, 53, 54]);
1104        btree_map.insert(52, vec![52, 53]);
1105        btree_map.insert(100, vec![100, 101, 102, 103]);
1106
1107        let snippets = DataSnippets::MultiBlock(btree_map);
1108
1109        assert_eq!(snippets.get(0..2), Some([0, 1].as_slice()));
1110        assert_eq!(snippets.get(1..3), Some([1, 2].as_slice()));
1111        assert_eq!(snippets.get(8..9), Some([8].as_slice()));
1112        assert_eq!(snippets.get(10..11), None);
1113        assert_eq!(snippets.get(50..51), Some([50].as_slice()));
1114        assert_eq!(snippets.get(51..53), Some([51, 52].as_slice()));
1115        assert_eq!(snippets.get(50..54), Some([50, 51, 52, 53].as_slice()));
1116        assert_eq!(snippets.get(52..54), Some([52, 53].as_slice()));
1117        assert_eq!(snippets.get(52..55), Some([52, 53, 54].as_slice()));
1118        assert_eq!(snippets.get(52..53), Some([52].as_slice()));
1119        assert_eq!(snippets.get(50..56), None);
1120        assert_eq!(snippets.get(100..101), Some([100].as_slice()));
1121        assert_eq!(snippets.get(101..103), Some([101, 102].as_slice()));
1122
1123        assert_eq!(
1124            snippets.get_with_context(0..2, 1),
1125            Some(([0, 1, 2].as_slice(), 0..2))
1126        );
1127
1128        assert_eq!(
1129            snippets.get_with_context(0..2, 2),
1130            Some(([0, 1, 2, 3].as_slice(), 0..2))
1131        );
1132
1133        assert_eq!(
1134            snippets.get_with_context(2..4, 2),
1135            Some(([0, 1, 2, 3, 4, 5].as_slice(), 2..4))
1136        );
1137
1138        assert_eq!(
1139            snippets.get_with_context(51..52, 3),
1140            Some(([50, 51, 52, 53, 54].as_slice(), 1..2))
1141        );
1142
1143        assert_eq!(
1144            snippets.get_with_context(102..103, 3),
1145            Some(([100, 101, 102, 103].as_slice(), 2..3))
1146        );
1147    }
1148
1149    #[test]
1150    fn snippets_singleblock() {
1151        let data = b"Lorem ipsum dolor sit amet".to_vec();
1152        let scanned_data = super::ScannedData::Vec(data);
1153        let snippets = DataSnippets::SingleBlock(scanned_data);
1154
1155        // Test get
1156        assert_eq!(snippets.get(6..11), Some(b"ipsum".as_slice()));
1157        assert_eq!(snippets.get(0..5), Some(b"Lorem".as_slice()));
1158        assert_eq!(snippets.get(20..26), Some(b"t amet".as_slice()));
1159        assert_eq!(snippets.get(27..30), None);
1160
1161        // Test get_with_context
1162        // context_size = 5
1163        assert_eq!(
1164            snippets.get_with_context(6..11, 5),
1165            Some((b"orem ipsum dolo".as_slice(), 5..10))
1166        );
1167        assert_eq!(
1168            snippets.get_with_context(0..5, 5),
1169            Some((b"Lorem ipsu".as_slice(), 0..5))
1170        );
1171        assert_eq!(
1172            snippets.get_with_context(20..26, 5),
1173            Some((b"or sit amet".as_slice(), 5..11))
1174        );
1175        assert_eq!(snippets.get_with_context(32..35, 5), None);
1176    }
1177}