Skip to main content

yara_x/compiler/
rules.rs

1use std::fmt;
2use std::io::{BufWriter, Read, Write};
3use std::ops::{Bound, RangeBounds};
4use std::slice::Iter;
5#[cfg(feature = "logging")]
6use std::time::Instant;
7
8use anyhow::anyhow;
9use daachorse::DoubleArrayAhoCorasick;
10#[cfg(feature = "logging")]
11use log::*;
12use regex_automata::meta::Regex;
13use rustc_hash::FxHashMap;
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15
16use crate::compiler::atoms::Atom;
17use crate::compiler::errors::SerializationError;
18use crate::compiler::report::CodeLoc;
19use crate::compiler::warnings::Warning;
20use crate::compiler::{
21    IdentId, Imports, LiteralId, NamespaceId, PatternId, RegexId, RegexSetId,
22    RuleId, SubPattern, SubPatternId,
23};
24use crate::models::PatternKind;
25use crate::re::{BckCodeLoc, FwdCodeLoc, RegexpAtom};
26use crate::string_pool::{BStringPool, StringPool};
27use crate::{Rule, re, teddy, types, wasm};
28
29/// Magic bytes prepended to any binary file generated by YARA-X.
30const MAGIC: &[u8] = b"YARA-X\0\0";
31
32/// Version of the serialization format.
33///
34/// This version is incremented every time a change is made to the binary
35/// format in a way that breaks backwards compatibility.
36const SERIALIZATION_VERSION: u32 = 2;
37
38/// Aho-Corasick automaton bundled with an optional Teddy scanner if the
39/// number of patterns is low enough. If the Teddy scanner is present, and
40/// the length of the scanned data allows it, the Teddy scanner is used
41/// because it is faster. In all other cases, the Aho-Corasick automaton is
42/// used.
43pub(crate) struct AhoCorasick {
44    pub(crate) daachorse: DoubleArrayAhoCorasick<u32>,
45    pub(crate) teddy: Option<teddy::Searcher>,
46}
47
48/// A set of YARA rules in compiled form.
49///
50/// This is the result from [`crate::Compiler::build`].
51#[derive(Serialize, Deserialize)]
52pub struct Rules {
53    /// Pool with identifiers used in the rules. Each identifier has its
54    /// own [`IdentId`], which can be used for retrieving the identifier
55    /// from the pool as a `&str`.
56    pub(in crate::compiler) ident_pool: StringPool<IdentId>,
57
58    /// Pool with the regular expressions used in the rules conditions. Each
59    /// regular expression has its own [`RegexId`]. Regular expressions
60    /// include the starting and ending slashes (`/`), and the modifiers
61    /// `i` and `s` if present (e.g: `/foobar/`, `/foo/i`, `/bar/s`).
62    pub(in crate::compiler) regex_pool: StringPool<RegexId>,
63
64    /// If `true`, the regular expressions in `regex_pool` are allowed to
65    /// contain invalid escape sequences.
66    pub(in crate::compiler) relaxed_re_syntax: bool,
67
68    /// Pool with literal strings used in the rules. Each literal has its
69    /// own [`LiteralId`], which can be used for retrieving the literal
70    /// string as `&BStr`.
71    pub(in crate::compiler) lit_pool: BStringPool<LiteralId>,
72
73    /// WASM module as in raw form.
74    pub(in crate::compiler) wasm_mod: Vec<u8>,
75
76    /// WASM module already compiled into native code for the current platform.
77    /// When the rules are serialized, the compiled module is included only if
78    /// the `native-code-serialization` is enabled.
79    #[serde(
80        serialize_with = "serialize_wasm_mod",
81        deserialize_with = "deserialize_wasm_mod"
82    )]
83    pub(in crate::compiler) compiled_wasm_mod: Option<wasm::runtime::Module>,
84
85    /// Vector with the names of all the imported modules. The vector contains
86    /// the [`IdentId`] corresponding to the module's identifier.
87    pub(in crate::compiler) imported_modules: Vec<IdentId>,
88
89    /// Vector containing all the compiled rules. A [`RuleId`] is an index
90    /// in this vector.
91    pub(in crate::compiler) rules: Vec<RuleInfo>,
92
93    /// Total number of patterns across all rules. This is equal to the last
94    /// [`PatternId`] +  1.
95    pub(in crate::compiler) num_patterns: usize,
96
97    /// Vector with all the sub-patterns from all rules. A [`SubPatternId`]
98    /// is an index in this vector. Each pattern is composed of one or more
99    /// sub-patterns, if any of the sub-patterns matches, the pattern matches.
100    ///
101    /// For example, when a text pattern is accompanied by both the `ascii`
102    /// and `wide` modifiers, two sub-patterns are generated for it: one for
103    /// the ascii variant, and the other for the wide variant.
104    ///
105    /// Each sub-pattern in this vector is accompanied by the [`PatternId`]
106    /// where the sub-pattern belongs to.
107    pub(in crate::compiler) sub_patterns: Vec<(PatternId, SubPattern)>,
108
109    /// Map that associates a `PatternId` to a certain file size bound.
110    ///
111    /// A condition like `filesize < 1000 and $a` only matches if `filesize`
112    /// is less than 1000. Therefore, the pattern `$a` does not need be
113    /// checked for files of size 1000 bytes or larger.
114    ///
115    /// In this case, the map will contain an entry associating `$a` to a
116    /// `FilesizeBounds` value like:
117    ///
118    /// `FilesizeBounds{start: Bound::Unbounded, end: Bound:Excluded(1000)}`.
119    pub(in crate::compiler) filesize_bounds:
120        FxHashMap<PatternId, FilesizeBounds>,
121
122    /// Map that associates a `PatternId` to a certain constraint on the
123    /// file header (e.g. magic bytes at offset 0), if any.
124    ///
125    /// A condition like `uint16(0) == 0x5A4D and $a` or `$mz at 0 and $a`
126    /// (where $mz = "MZ") only matches if the file starts with "MZ" (0x5A4D).
127    /// In this case, the map will contain an entry associating `$a` to a
128    /// `HeaderConstraint` that requires the file to start with those two
129    /// bytes.
130    ///
131    /// This allows skipping pattern checks entirely if the scanned data
132    /// doesn't start with the expected header prefix.
133    pub(in crate::compiler) header_constraints:
134        FxHashMap<PatternId, HeaderConstraint>,
135
136    /// Vector that contains the [`SubPatternId`] for sub-patterns that can
137    /// match only at a fixed offset within the scanned data. These sub-patterns
138    /// are not added to the Aho-Corasick automaton.
139    pub(in crate::compiler) anchored_sub_patterns: Vec<SubPatternId>,
140
141    /// A vector that contains all the atoms extracted from the patterns. Each
142    /// atom has an associated [`SubPatternId`] that indicates the sub-pattern
143    /// it belongs to.
144    pub(in crate::compiler) atoms: Vec<SubPatternAtom>,
145
146    /// A vector that contains the code for all regexp patterns (this includes
147    /// hex patterns which are just a special case of regexp). The code for
148    /// each regexp is appended to the vector, during the compilation process
149    /// and the atoms extracted from the regexp contain offsets within this
150    /// vector. This vector contains both forward and backward code.
151    pub(in crate::compiler) re_code: Vec<u8>,
152
153    /// A [`types::Struct`] in serialized form that contains all the global
154    /// variables. Each field in the structure corresponds to a global variable
155    /// defined at compile time using [`crate::compiler::Compiler`].
156    pub(in crate::compiler) serialized_globals: Vec<u8>,
157
158    /// Aho-Corasick automaton containing the atoms extracted from the patterns.
159    /// This allows to search for all the atoms in the scanned data at the same
160    /// time in an efficient manner. The automaton is not serialized during when
161    /// [`Rules::serialize`] is called, it needs to be wrapped in [`Option`] so
162    /// that we can use `#[serde(skip)]` on it because [`AhoCorasick`] doesn't
163    /// implement the [`Default`] trait.
164    #[serde(skip)]
165    pub(in crate::compiler) ac: Option<AhoCorasick>,
166
167    /// Warnings that were produced while compiling these rules. These warnings
168    /// are not serialized, rules that are obtained by deserializing previously
169    /// serialized rules won't have any warnings.
170    #[serde(skip)]
171    pub(in crate::compiler) warnings: Vec<Warning>,
172
173    /// Grouped `RegexSet` persistent definitions.
174    ///
175    /// Populated during `Compiler::build`, each entry maps a unique
176    /// `RegexSetId` to an ordered list of `RegexpId`s. All regular
177    /// expressions in a given set match the exact same target expression
178    /// in the source code, allowing them to be compiled into a unified
179    /// set automata for single-pass evaluation.
180    pub(in crate::compiler) regex_sets: FxHashMap<RegexSetId, Vec<RegexId>>,
181
182    /// BitVec where the N-th bit indicates whether the pattern with
183    /// PatternId = N is a fast-scan pattern.
184    ///
185    /// A pattern can be fast-scanned if its occurrences are only evaluated
186    /// as simple boolean checks (e.g. `$a`), meaning the scanner can stop
187    /// tracking matches for it once the first match has been found. If a
188    /// pattern is used in a context that requires tracking all matches (such
189    /// as count `#a`, offset `@a`, length `!a`, anchored checks, or loop
190    /// equivalents), it cannot be fast-scanned.
191    pub(in crate::compiler) fast_scan_patterns: bitvec::vec::BitVec,
192}
193
194impl Rules {
195    /// An iterator that yields the name of the modules imported by the
196    /// rules.
197    pub fn imports(&self) -> Imports<'_> {
198        Imports {
199            iter: self.imported_modules.iter(),
200            ident_pool: &self.ident_pool,
201        }
202    }
203
204    /// Warnings produced while compiling these rules.
205    pub fn warnings(&self) -> &[Warning] {
206        self.warnings.as_slice()
207    }
208
209    /// Serializes the rules as a sequence of bytes.
210    ///
211    /// The [`Rules`] can be restored back by passing the bytes to
212    /// [`Rules::deserialize`].
213    pub fn serialize(&self) -> Result<Vec<u8>, SerializationError> {
214        let mut bytes = Vec::new();
215        self.serialize_into(&mut bytes)?;
216        Ok(bytes)
217    }
218
219    /// Deserializes the rules from a sequence of bytes produced by
220    /// [`Rules::serialize`].
221    ///
222    /// # Safety
223    ///
224    /// As long as you are deserializing rules from binary content produced
225    /// by [`Rules::serialize`] you are safe. But you should never attempt
226    /// to do so from binary content that was produced or could be manipulated
227    /// by a third party. This implies a security risk and your program may
228    /// panic during scanning.
229    pub fn deserialize<B>(bytes: B) -> Result<Self, SerializationError>
230    where
231        B: AsRef<[u8]>,
232    {
233        let bytes = bytes.as_ref();
234        let version_offset = MAGIC.len();
235        let data_offset = version_offset + size_of::<u32>();
236
237        if bytes.len() < data_offset || &bytes[0..version_offset] != MAGIC {
238            return Err(SerializationError::InvalidFormat);
239        }
240
241        let version = u32::from_le_bytes(
242            bytes[version_offset..data_offset].try_into().unwrap(),
243        );
244
245        if version != SERIALIZATION_VERSION {
246            return Err(SerializationError::InvalidVersion {
247                expected: SERIALIZATION_VERSION,
248                actual: version,
249            });
250        }
251
252        #[cfg(feature = "logging")]
253        let start = Instant::now();
254
255        // Skip the header and deserialize the remaining data.
256        let (mut rules, _len): (Self, usize) =
257            bincode::serde::decode_from_slice(
258                &bytes[data_offset..],
259                bincode::config::standard(),
260            )?;
261
262        #[cfg(feature = "logging")]
263        info!("Deserialization time: {:?}", Instant::elapsed(&start));
264
265        // `rules.compiled_wasm_mod` can be `None` for two reasons:
266        //
267        //  1- The rules were serialized without compiled rules (i.e: the
268        //     `native-code-serialization` feature was disabled, which is
269        //     the default).
270        //
271        //  2- The rules were serialized with compiled rules, but they were
272        //     compiled for a different platform, and `deserialize_wasm_mod`
273        //     returned `None`.
274        //
275        // In both cases we try to build the module again from the data in
276        // `rules.wasm_mode`.
277        if rules.compiled_wasm_mod.is_none() {
278            #[cfg(feature = "logging")]
279            let start = Instant::now();
280
281            rules.compiled_wasm_mod = Some(
282                wasm::runtime::Module::from_binary(
283                    wasm::get_engine(),
284                    rules.wasm_mod.as_slice(),
285                )
286                .map_err(|e| SerializationError::from(anyhow!(e)))?,
287            );
288
289            #[cfg(feature = "logging")]
290            info!("WASM build time: {:?}", Instant::elapsed(&start));
291        }
292
293        rules.build_ac_automaton();
294
295        // Make sure that the maximum SubPatternId is within the boundaries
296        // of sub_patterns array. This check is important because during
297        // the scanning phase we use SubPatternId as indexes in the array
298        // without boundary checks for better performance.
299        let max_sub_pattern_id = rules
300            .atoms
301            .iter()
302            .map(|atom| atom.sub_pattern_id)
303            .max()
304            .unwrap_or(SubPatternId(0));
305
306        if rules.sub_patterns.len() < max_sub_pattern_id.0 as usize {
307            return Err(SerializationError::InvalidFormat);
308        }
309
310        Ok(rules)
311    }
312
313    /// Serializes the rules into a `writer`.
314    pub fn serialize_into<W>(
315        &self,
316        writer: W,
317    ) -> Result<(), SerializationError>
318    where
319        W: Write,
320    {
321        let mut writer = BufWriter::new(writer);
322
323        // Write file header.
324        writer.write_all(MAGIC)?;
325
326        // Write version.
327        writer.write_all(&SERIALIZATION_VERSION.to_le_bytes())?;
328
329        bincode::serde::encode_into_std_write(
330            self,
331            &mut writer,
332            bincode::config::standard(),
333        )?;
334
335        Ok(())
336    }
337
338    /// Deserializes the rules from a `reader`.
339    pub fn deserialize_from<R>(
340        mut reader: R,
341    ) -> Result<Self, SerializationError>
342    where
343        R: Read,
344    {
345        let mut bytes = Vec::new();
346        let _ = reader.read_to_end(&mut bytes)?;
347        Self::deserialize(bytes)
348    }
349
350    /// Returns an iterator that yields the compiled rules.
351    ///
352    /// ```rust
353    /// # use yara_x::Compiler;
354    /// let mut compiler = Compiler::new();
355    ///
356    /// assert!(compiler
357    ///     .add_source("rule foo {condition: true}")
358    ///     .unwrap()
359    ///     .add_source("rule bar {condition: true}")
360    ///     .is_ok());
361    ///
362    /// let rules = compiler.build();
363    /// let mut iter = rules.iter();
364    ///
365    /// assert_eq!(iter.len(), 2);
366    /// assert_eq!(iter.next().map(|r| r.identifier()), Some("foo"));
367    /// assert_eq!(iter.next().map(|r| r.identifier()), Some("bar"));
368    /// ```
369    pub fn iter(&self) -> RulesIter<'_> {
370        RulesIter { rules: self, iterator: self.rules.iter() }
371    }
372
373    /// Returns a [`RuleInfo`] given its [`RuleId`].
374    ///
375    /// # Panics
376    ///
377    /// If no rule with such [`RuleId`] exists.
378    pub(crate) fn get(&self, rule_id: RuleId) -> &RuleInfo {
379        self.rules.get(rule_id.0 as usize).unwrap()
380    }
381
382    /// Returns a regular expression by [`RegexId`].
383    ///
384    /// # Panics
385    ///
386    /// If no regular expression with such [`RegexId`] exists.
387    #[inline]
388    pub(crate) fn get_regexp(&self, regexp_id: RegexId) -> Regex {
389        let re = types::Regexp::new(self.regex_pool.get(regexp_id).unwrap());
390
391        let parser = re::parser::Parser::new()
392            .relaxed_re_syntax(self.relaxed_re_syntax);
393
394        let hir = parser.parse(&re).unwrap().into_inner();
395
396        // Set a size limit for the NFA automata. The default limit (10MB) is
397        // too small for certain regexps seen in YARA rules in the wild, see:
398        // https://github.com/VirusTotal/yara-x/issues/85
399        let config = regex_automata::meta::Config::new()
400            .nfa_size_limit(Some(50 * 1024 * 1024));
401
402        regex_automata::meta::Builder::new()
403            .configure(config)
404            .build_from_hir(&hir)
405            .unwrap_or_else(|err| {
406                panic!("error compiling regex `{}`: {:#?}", re.as_str(), err)
407            })
408    }
409
410    /// Returns a compiled multi-pattern `RegexSet` for a given `RegexSetId`.
411    #[inline]
412    pub(crate) fn get_regex_set(
413        &self,
414        set_id: RegexSetId,
415    ) -> regex::bytes::RegexSet {
416        let re_ids = self.regex_sets.get(&set_id).unwrap();
417        let mut patterns = Vec::with_capacity(re_ids.len());
418
419        for &re_id in re_ids {
420            let re = types::Regexp::new(self.regex_pool.get(re_id).unwrap());
421            let parser = re::parser::Parser::new()
422                .relaxed_re_syntax(self.relaxed_re_syntax);
423            let hir = parser.parse(&re).unwrap().into_inner();
424            patterns.push(hir.to_string());
425        }
426
427        regex::bytes::RegexSetBuilder::new(patterns)
428            .size_limit(1024 * 1024 * 1024)
429            .build()
430            .unwrap_or_else(|err| {
431                panic!("error compiling RegexSet: {:#?}", err)
432            })
433    }
434
435    /// Returns a sub-pattern by [`SubPatternId`].
436    #[inline]
437    pub(crate) fn get_sub_pattern(
438        &self,
439        sub_pattern_id: SubPatternId,
440    ) -> &(PatternId, SubPattern) {
441        unsafe { self.sub_patterns.get_unchecked(sub_pattern_id.0 as usize) }
442    }
443
444    /// Given a [`SubPatternId`], returns the [`RuleId`] corresponding to the
445    /// rule that contains the sub-pattern, and the [`IdentId`] for the pattern's
446    /// identifier.
447    ///
448    /// This operation is slow, because it implies iterating over all the rules
449    /// and their sub-patterns until finding the one we are looking for.
450    #[cfg(feature = "logging")]
451    pub(crate) fn get_rule_and_pattern_by_sub_pattern_id(
452        &self,
453        sub_pattern_id: SubPatternId,
454    ) -> Option<(RuleId, IdentId)> {
455        let (target_pattern_id, _) = self.get_sub_pattern(sub_pattern_id);
456        for (rule_id, rule) in self.rules.iter().enumerate() {
457            for p in &rule.patterns {
458                if p.pattern_id == *target_pattern_id {
459                    return Some((rule_id.into(), p.ident_id));
460                };
461            }
462        }
463        None
464    }
465
466    #[cfg(feature = "rules-profiling")]
467    #[inline]
468    pub(crate) fn rules(&self) -> &[RuleInfo] {
469        self.rules.as_slice()
470    }
471
472    #[inline]
473    pub(crate) fn atoms(&self) -> &[SubPatternAtom] {
474        self.atoms.as_slice()
475    }
476
477    #[inline]
478    pub(crate) fn anchored_sub_patterns(&self) -> &[SubPatternId] {
479        self.anchored_sub_patterns.as_slice()
480    }
481
482    #[inline]
483    pub(crate) fn re_code(&self) -> &[u8] {
484        self.re_code.as_slice()
485    }
486
487    #[inline]
488    pub(crate) fn num_rules(&self) -> usize {
489        self.rules.len()
490    }
491
492    #[inline]
493    pub(crate) fn num_patterns(&self) -> usize {
494        self.num_patterns
495    }
496
497    /// Returns the Aho-Corasick automaton that allows to search for pattern
498    /// atoms.
499    #[inline]
500    pub(crate) fn ac_automaton(&self) -> &AhoCorasick {
501        self.ac.as_ref().expect("Aho-Corasick automaton not compiled")
502    }
503
504    pub(crate) fn build_ac_automaton(&mut self) {
505        if self.ac.is_some() {
506            return;
507        }
508
509        #[cfg(feature = "logging")]
510        let start = Instant::now();
511
512        #[cfg(feature = "logging")]
513        let mut num_atoms = [0_usize; 6];
514
515        #[cfg(feature = "logging")]
516        for x in &self.atoms {
517            match x.atom.len() {
518                atom_len @ 0..=4 => num_atoms[atom_len] += 1,
519                _ => num_atoms[num_atoms.len() - 1] += 1,
520            }
521
522            if x.atom.len() < 2 {
523                let (rule_id, pattern_ident_id) = self
524                    .get_rule_and_pattern_by_sub_pattern_id(x.sub_pattern_id)
525                    .unwrap();
526
527                let rule = self.get(rule_id);
528
529                info!(
530                    "Very short atom in pattern `{}` in rule `{}:{}` (length: {})",
531                    self.ident_pool.get(pattern_ident_id).unwrap(),
532                    self.ident_pool.get(rule.namespace_ident_id).unwrap(),
533                    self.ident_pool.get(rule.ident_id).unwrap(),
534                    x.atom.len()
535                );
536            }
537        }
538
539        // The Teddy algorithm can't be used in all cases. It will be used if:
540        // - The number of atoms is between 1 and 64.
541        // - None of the atoms is empty.
542        let use_teddy = self.atoms.len() <= 64
543            && !self.atoms.is_empty()
544            && !self.atoms.iter().any(|x| x.atom.as_ref().is_empty());
545
546        let teddy_searcher = if use_teddy {
547            let mut teddy_builder = teddy::Builder::new();
548            self.atoms.iter().for_each(|x| teddy_builder.add(x.atom.as_ref()));
549            teddy_builder.build()
550        } else {
551            None
552        };
553
554        let atoms = self.atoms.iter().map(|x| x.atom.as_ref());
555        let ac = DoubleArrayAhoCorasick::new(atoms)
556            .expect("failed to build Aho-Corasick automaton");
557
558        self.ac = Some(AhoCorasick { daachorse: ac, teddy: teddy_searcher });
559
560        #[cfg(feature = "logging")]
561        {
562            info!(
563                "Aho-Corasick automaton build time: {:?}",
564                Instant::elapsed(&start)
565            );
566
567            info!("Number of rules: {}", self.num_rules());
568            info!("Number of patterns: {}", self.num_patterns());
569            info!(
570                "Number of anchored sub-patterns: {}",
571                self.anchored_sub_patterns.len()
572            );
573            info!("Number of atoms: {}", self.atoms.len());
574            info!("Atoms with len = 0: {}", num_atoms[0]);
575            info!("Atoms with len = 1: {}", num_atoms[1]);
576            info!("Atoms with len = 2: {}", num_atoms[2]);
577            info!("Atoms with len = 3: {}", num_atoms[3]);
578            info!("Atoms with len = 4: {}", num_atoms[4]);
579            info!("Atoms with len > 4: {}", num_atoms[5]);
580        }
581    }
582
583    #[inline]
584    pub(crate) fn lit_pool(&self) -> &BStringPool<LiteralId> {
585        &self.lit_pool
586    }
587
588    #[inline]
589    pub(crate) fn ident_pool(&self) -> &StringPool<IdentId> {
590        &self.ident_pool
591    }
592
593    #[inline]
594    pub(crate) fn globals(&self) -> types::Struct {
595        let (globals, _): (types::Struct, usize) =
596            bincode::serde::decode_from_slice(
597                self.serialized_globals.as_slice(),
598                bincode::config::standard(),
599            )
600            .expect("error deserializing global variables");
601        globals
602    }
603
604    #[inline]
605    pub(crate) fn wasm_mod(&self) -> &wasm::runtime::Module {
606        self.compiled_wasm_mod.as_ref().unwrap()
607    }
608
609    #[inline]
610    pub(crate) fn filesize_bounds(
611        &self,
612        pattern_id: PatternId,
613    ) -> Option<&FilesizeBounds> {
614        self.filesize_bounds.get(&pattern_id)
615    }
616
617    #[inline]
618    pub(crate) fn header_constraints(
619        &self,
620        pattern_id: PatternId,
621    ) -> Option<&HeaderConstraint> {
622        self.header_constraints.get(&pattern_id)
623    }
624
625    #[inline]
626    pub(crate) fn is_fast_scan(&self, pattern_id: PatternId) -> bool {
627        *self.fast_scan_patterns.get(usize::from(pattern_id)).unwrap()
628    }
629}
630
631#[cfg(feature = "native-code-serialization")]
632fn serialize_wasm_mod<S>(
633    wasm_mod: &Option<wasm::runtime::Module>,
634    serializer: S,
635) -> Result<S::Ok, S::Error>
636where
637    S: Serializer,
638{
639    if let Some(wasm_mod) = wasm_mod {
640        let bytes = wasm_mod
641            .serialize()
642            .map_err(|err| serde::ser::Error::custom(err.to_string()))?;
643
644        serializer.serialize_some(bytes.as_slice())
645    } else {
646        serializer.serialize_none()
647    }
648}
649
650#[cfg(not(feature = "native-code-serialization"))]
651fn serialize_wasm_mod<S>(
652    _wasm_mod: &Option<wasm::runtime::Module>,
653    serializer: S,
654) -> Result<S::Ok, S::Error>
655where
656    S: Serializer,
657{
658    serializer.serialize_none()
659}
660
661pub fn deserialize_wasm_mod<'de, D>(
662    deserializer: D,
663) -> Result<Option<wasm::runtime::Module>, D::Error>
664where
665    D: Deserializer<'de>,
666{
667    let bytes: Option<&[u8]> = Deserialize::deserialize(deserializer)?;
668    let module = if let Some(bytes) = bytes {
669        wasm::runtime::Module::deserialize(wasm::get_engine(), bytes).ok()
670    } else {
671        None
672    };
673
674    Ok(module)
675}
676
677/// Iterator that yields the of the compiled rules.
678pub struct RulesIter<'a> {
679    rules: &'a Rules,
680    iterator: Iter<'a, RuleInfo>,
681}
682
683impl<'a> Iterator for RulesIter<'a> {
684    type Item = Rule<'a, 'a>;
685
686    fn next(&mut self) -> Option<Self::Item> {
687        Some(Rule {
688            ctx: None,
689            rules: self.rules,
690            rule_info: self.iterator.next()?,
691        })
692    }
693}
694
695impl ExactSizeIterator for RulesIter<'_> {
696    #[inline]
697    fn len(&self) -> usize {
698        self.iterator.len()
699    }
700}
701
702impl fmt::Debug for Rules {
703    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704        for (id, rule) in self.rules.iter().enumerate() {
705            let name = self.ident_pool.get(rule.ident_id).unwrap();
706            let namespace =
707                self.ident_pool.get(rule.namespace_ident_id).unwrap();
708            writeln!(f, "RuleId({id})")?;
709            writeln!(f, "  namespace: {namespace}")?;
710            writeln!(f, "  name: {name}")?;
711            writeln!(f, "  patterns:")?;
712            for pattern in &rule.patterns {
713                let ident = self.ident_pool.get(pattern.ident_id).unwrap();
714                writeln!(f, "    {:?} {ident} ", pattern.pattern_id)?;
715            }
716        }
717
718        for (id, (pattern_id, _)) in self.sub_patterns.iter().enumerate() {
719            writeln!(f, "SubPatternId({id}) -> {pattern_id:?}")?;
720        }
721
722        Ok(())
723    }
724}
725
726/// Metadata values.
727#[derive(Serialize, Deserialize)]
728pub(crate) enum MetaValue {
729    Bool(bool),
730    Integer(i64),
731    Float(f64),
732    String(LiteralId),
733    Bytes(LiteralId),
734}
735
736/// Information about each of the individual rules included in [`Rules`].
737#[derive(Serialize, Deserialize)]
738pub(crate) struct RuleInfo {
739    /// The ID of the namespace the rule belongs to.
740    pub namespace_id: NamespaceId,
741    /// The ID of the rule namespace in the identifiers pool.
742    pub namespace_ident_id: IdentId,
743    /// The ID of the rule identifier in the identifiers pool.
744    pub ident_id: IdentId,
745    /// Tags associated to the rule.
746    pub tags: Vec<IdentId>,
747    /// Reference to the rule identifier in the source code. This field is
748    /// ignored while serializing and deserializing compiles rules, as it
749    /// is used only during the compilation phase, but not during the scan
750    /// phase.
751    #[serde(skip)]
752    pub ident_ref: CodeLoc,
753    /// Metadata associated to the rule.
754    pub metadata: Vec<(IdentId, MetaValue)>,
755    /// Vector with all the patterns defined by this rule. The bool in the
756    /// tuple indicates if the pattern is private.
757    pub patterns: Vec<PatternInfo>,
758    /// Number of private patterns in the rule. The number of non-private
759    /// patterns can be computed as patterns.len - num_private_patterns.
760    pub num_private_patterns: usize,
761    /// True if the rule is global.
762    pub is_global: bool,
763    /// True if the rule is private.
764    pub is_private: bool,
765}
766
767/// Information about each of pattern in a rule.
768#[derive(Serialize, Deserialize)]
769pub(crate) struct PatternInfo {
770    /// Unique ID for this pattern.
771    pub pattern_id: PatternId,
772    /// The pattern identifier.
773    pub ident_id: IdentId,
774    /// Indicates if the pattern is text, hex or regexp.
775    pub kind: PatternKind,
776    /// True if the pattern is private.
777    pub is_private: bool,
778}
779
780/// Describes the bounds for `filesize` imposed by a rule condition.
781///
782/// For example, the condition `filesize < 1000 and $a` only matches files
783/// smaller than 10MB. That would be represented by:
784///
785/// ```text
786/// FilesizeBounds { start: Bound::Unbounded, end: Bound::Excluded(1000) }
787/// ```
788///
789/// In contrast, the condition `filesize < 1000 or $a` does not any bounds
790/// to `filesize`, since the use of `or` allows files larger than
791/// 10MB to also match. This case is represented by:
792///
793/// ```text
794/// FilesizeBounds { start: Bound::Unbounded, end: Bound::Unbounded }
795/// ```
796#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Hash, Eq)]
797pub(crate) struct FilesizeBounds {
798    start: Bound<i64>,
799    end: Bound<i64>,
800}
801
802impl Default for FilesizeBounds {
803    fn default() -> Self {
804        Self { start: Bound::Unbounded, end: Bound::Unbounded }
805    }
806}
807
808impl<T: RangeBounds<i64>> From<T> for FilesizeBounds {
809    fn from(value: T) -> Self {
810        Self {
811            start: value.start_bound().cloned(),
812            end: value.end_bound().cloned(),
813        }
814    }
815}
816
817impl FilesizeBounds {
818    pub fn unbounded(&self) -> bool {
819        matches!(self.start, Bound::Unbounded)
820            && matches!(self.end, Bound::Unbounded)
821    }
822
823    pub fn contains(&self, value: i64) -> bool {
824        let start_ok = match self.start {
825            Bound::Included(start) => value >= start,
826            Bound::Excluded(start) => value > start,
827            Bound::Unbounded => true,
828        };
829
830        let end_ok = match self.end {
831            Bound::Included(end) => value <= end,
832            Bound::Excluded(end) => value < end,
833            Bound::Unbounded => true,
834        };
835
836        start_ok && end_ok
837    }
838    pub fn max_start(&mut self, bound: Bound<i64>) -> &mut Self {
839        match (&self.start, &bound) {
840            (Bound::Included(current), Bound::Included(new)) => {
841                if new > current {
842                    self.start = Bound::Included(*new);
843                }
844            }
845            (Bound::Included(current), Bound::Excluded(new)) => {
846                if new >= current {
847                    self.start = Bound::Excluded(*new);
848                }
849            }
850            (Bound::Excluded(current), Bound::Included(new)) => {
851                if new > current {
852                    self.start = Bound::Included(*new);
853                }
854            }
855            (Bound::Excluded(current), Bound::Excluded(new)) => {
856                if new > current {
857                    self.start = Bound::Excluded(*new);
858                }
859            }
860            (Bound::Unbounded, new) => {
861                self.start = *new;
862            }
863            (_, Bound::Unbounded) => {}
864        }
865        self
866    }
867
868    pub fn min_end(&mut self, bound: Bound<i64>) -> &mut Self {
869        match (&self.end, &bound) {
870            (Bound::Included(current), Bound::Included(new)) => {
871                if new < current {
872                    self.end = Bound::Included(*new);
873                }
874            }
875            (Bound::Included(current), Bound::Excluded(new)) => {
876                if new <= current {
877                    self.end = Bound::Excluded(*new);
878                }
879            }
880            (Bound::Excluded(current), Bound::Included(new)) => {
881                if new < current {
882                    self.end = Bound::Included(*new);
883                }
884            }
885            (Bound::Excluded(current), Bound::Excluded(new)) => {
886                if new < current {
887                    self.end = Bound::Excluded(*new)
888                }
889            }
890            (Bound::Unbounded, new) => {
891                self.end = *new;
892            }
893            (_, Bound::Unbounded) => {}
894        }
895        self
896    }
897}
898
899/// Describes the requirements on the file header imposed by a rule condition.
900///
901/// For example, the condition `uint32(0) == 0x464c457f` requires that the first
902/// 4 bytes of the file are `0x7f, 0x45, 0x4c, 0x46`.
903#[derive(
904    Debug, PartialEq, Serialize, Deserialize, Clone, Hash, Eq, Default,
905)]
906pub(crate) enum HeaderConstraint {
907    #[default]
908    Unconstrained,
909    Unsatisfiable,
910    Constrained(Vec<u8>),
911}
912
913impl HeaderConstraint {
914    pub fn unconstrained(&self) -> bool {
915        matches!(self, Self::Unconstrained)
916    }
917
918    pub fn is_satisfied(&self, data: &[u8]) -> bool {
919        match self {
920            Self::Unconstrained => true,
921            Self::Unsatisfiable => false,
922            Self::Constrained(bytes) => data.starts_with(bytes),
923        }
924    }
925}
926
927/// Represents an atom extracted from a pattern and added to the Aho-Corasick
928/// automata.
929///
930/// Each time the Aho-Corasick finds one of these atoms, it proceeds to verify
931/// if the corresponding sub-pattern actually matches or not. The verification
932/// process depend on the type of sub-pattern.
933#[derive(Serialize, Deserialize)]
934pub(crate) struct SubPatternAtom {
935    /// The [`SubPatternId`] that identifies the sub-pattern this atom
936    /// belongs to.
937    sub_pattern_id: SubPatternId,
938    /// The atom itself.
939    atom: Atom,
940    /// The index within `re_code` where the forward code for this atom starts.
941    fwd_code: Option<FwdCodeLoc>,
942    /// The index within `re_code` where the backward code for this atom starts.
943    bck_code: Option<BckCodeLoc>,
944}
945
946impl SubPatternAtom {
947    #[inline]
948    pub(crate) fn from_atom(sub_pattern_id: SubPatternId, atom: Atom) -> Self {
949        Self { sub_pattern_id, atom, bck_code: None, fwd_code: None }
950    }
951
952    pub(crate) fn from_regexp_atom(
953        sub_pattern_id: SubPatternId,
954        value: RegexpAtom,
955    ) -> Self {
956        Self {
957            sub_pattern_id,
958            atom: value.atom,
959            fwd_code: value.fwd_code,
960            bck_code: value.bck_code,
961        }
962    }
963
964    #[inline]
965    pub(crate) fn sub_pattern_id(&self) -> SubPatternId {
966        self.sub_pattern_id
967    }
968
969    #[cfg(feature = "exact-atoms")]
970    #[inline]
971    pub(crate) fn is_exact(&self) -> bool {
972        self.atom.is_exact()
973    }
974
975    #[inline]
976    pub(crate) fn len(&self) -> usize {
977        self.atom.len()
978    }
979
980    #[inline]
981    pub(crate) fn backtrack(&self) -> usize {
982        self.atom.backtrack() as usize
983    }
984
985    #[inline]
986    pub(crate) fn as_slice(&self) -> &[u8] {
987        self.atom.as_ref()
988    }
989
990    #[inline]
991    pub(crate) fn fwd_code(&self) -> Option<FwdCodeLoc> {
992        self.fwd_code
993    }
994
995    #[inline]
996    pub(crate) fn bck_code(&self) -> Option<BckCodeLoc> {
997        self.bck_code
998    }
999}