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
29const MAGIC: &[u8] = b"YARA-X\0\0";
31
32const SERIALIZATION_VERSION: u32 = 2;
37
38pub(crate) struct AhoCorasick {
44 pub(crate) daachorse: DoubleArrayAhoCorasick<u32>,
45 pub(crate) teddy: Option<teddy::Searcher>,
46}
47
48#[derive(Serialize, Deserialize)]
52pub struct Rules {
53 pub(in crate::compiler) ident_pool: StringPool<IdentId>,
57
58 pub(in crate::compiler) regex_pool: StringPool<RegexId>,
63
64 pub(in crate::compiler) relaxed_re_syntax: bool,
67
68 pub(in crate::compiler) lit_pool: BStringPool<LiteralId>,
72
73 pub(in crate::compiler) wasm_mod: Vec<u8>,
75
76 #[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 pub(in crate::compiler) imported_modules: Vec<IdentId>,
88
89 pub(in crate::compiler) rules: Vec<RuleInfo>,
92
93 pub(in crate::compiler) num_patterns: usize,
96
97 pub(in crate::compiler) sub_patterns: Vec<(PatternId, SubPattern)>,
108
109 pub(in crate::compiler) filesize_bounds:
120 FxHashMap<PatternId, FilesizeBounds>,
121
122 pub(in crate::compiler) header_constraints:
134 FxHashMap<PatternId, HeaderConstraint>,
135
136 pub(in crate::compiler) anchored_sub_patterns: Vec<SubPatternId>,
140
141 pub(in crate::compiler) atoms: Vec<SubPatternAtom>,
145
146 pub(in crate::compiler) re_code: Vec<u8>,
152
153 pub(in crate::compiler) serialized_globals: Vec<u8>,
157
158 #[serde(skip)]
165 pub(in crate::compiler) ac: Option<AhoCorasick>,
166
167 #[serde(skip)]
171 pub(in crate::compiler) warnings: Vec<Warning>,
172
173 pub(in crate::compiler) regex_sets: FxHashMap<RegexSetId, Vec<RegexId>>,
181
182 pub(in crate::compiler) fast_scan_patterns: bitvec::vec::BitVec,
192}
193
194impl Rules {
195 pub fn imports(&self) -> Imports<'_> {
198 Imports {
199 iter: self.imported_modules.iter(),
200 ident_pool: &self.ident_pool,
201 }
202 }
203
204 pub fn warnings(&self) -> &[Warning] {
206 self.warnings.as_slice()
207 }
208
209 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 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 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 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 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 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 writer.write_all(MAGIC)?;
325
326 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 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 pub fn iter(&self) -> RulesIter<'_> {
370 RulesIter { rules: self, iterator: self.rules.iter() }
371 }
372
373 pub(crate) fn get(&self, rule_id: RuleId) -> &RuleInfo {
379 self.rules.get(rule_id.0 as usize).unwrap()
380 }
381
382 #[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 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 #[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 #[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 #[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 #[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 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
677pub 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#[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#[derive(Serialize, Deserialize)]
738pub(crate) struct RuleInfo {
739 pub namespace_id: NamespaceId,
741 pub namespace_ident_id: IdentId,
743 pub ident_id: IdentId,
745 pub tags: Vec<IdentId>,
747 #[serde(skip)]
752 pub ident_ref: CodeLoc,
753 pub metadata: Vec<(IdentId, MetaValue)>,
755 pub patterns: Vec<PatternInfo>,
758 pub num_private_patterns: usize,
761 pub is_global: bool,
763 pub is_private: bool,
765}
766
767#[derive(Serialize, Deserialize)]
769pub(crate) struct PatternInfo {
770 pub pattern_id: PatternId,
772 pub ident_id: IdentId,
774 pub kind: PatternKind,
776 pub is_private: bool,
778}
779
780#[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#[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#[derive(Serialize, Deserialize)]
934pub(crate) struct SubPatternAtom {
935 sub_pattern_id: SubPatternId,
938 atom: Atom,
940 fwd_code: Option<FwdCodeLoc>,
942 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}