yara_x/compiler/warnings.rs
1#![cfg_attr(any(), rustfmt::skip)]
2#![allow(clippy::duplicated_attributes)]
3
4use std::fmt::{Debug, Display, Formatter};
5
6use serde::Serialize;
7use thiserror::Error;
8
9use yara_x_macros::ErrorEnum;
10use yara_x_macros::ErrorStruct;
11
12pub(crate) use crate::compiler::report::{Level, Patch, Report, ReportBuilder, CodeLoc, Label, Footer};
13
14/// A warning raised while compiling YARA rules.
15#[allow(missing_docs)]
16#[non_exhaustive]
17#[derive(ErrorEnum, Error, PartialEq, Eq)]
18#[derive(Serialize)]
19#[serde(tag = "type")]
20pub enum Warning {
21 AmbiguousExpression(Box<AmbiguousExpression>),
22 BooleanIntegerComparison(Box<BooleanIntegerComparison>),
23 ConsecutiveJumps(Box<ConsecutiveJumps>),
24 DeprecatedField(Box<DeprecatedField>),
25 DuplicateImport(Box<DuplicateImport>),
26 GlobalRuleMisuse(Box<GlobalRuleMisuse>),
27 IgnoredModule(Box<IgnoredModule>),
28 IgnoredRule(Box<IgnoredRule>),
29 InvalidMetadata(Box<InvalidMetadata>),
30 InvalidRuleName(Box<InvalidRuleName>),
31 InvalidTag(Box<InvalidTag>),
32 InvariantBooleanExpression(Box<InvariantBooleanExpression>),
33 MissingMetadata(Box<MissingMetadata>),
34 NonBooleanAsBoolean(Box<NonBooleanAsBoolean>),
35 PotentiallySlowLoop(Box<PotentiallySlowLoop>),
36 PotentiallyUnsatisfiableExpression(Box<PotentiallyUnsatisfiableExpression>),
37 RedundantCaseModifier(Box<RedundantCaseModifier>),
38 SlowPattern(Box<SlowPattern>),
39 TextPatternAsHex(Box<TextPatternAsHex>),
40 TooManyIterations(Box<TooManyIterations>),
41 UnknownTag(Box<UnknownTag>),
42 UnsatisfiableExpression(Box<UnsatisfiableExpression>),
43 UnusedIdentifier(Box<UnusedIdentifier>),
44}
45
46/// A hex pattern contains two or more consecutive jumps.
47///
48/// For instance, in `{01 02 [0-2] [1-3] 03 04 }` the jumps `[0-2]` and `[1-3]`
49/// appear one after the other. Consecutive jumps are useless, and they can be
50/// folded into a single one. In this case they can be replaced by `[1-5]`.
51///
52/// ## Example
53///
54/// ```text
55/// warning[consecutive_jumps]: consecutive jumps in hex pattern `$a`
56/// --> line:3:18
57/// |
58/// 3 | $a = { 0F 84 [4] [0-7] 8D }
59/// | --------- these consecutive jumps will be treated as [4-11]
60/// |
61/// ```
62#[derive(ErrorStruct, Debug, PartialEq, Eq)]
63#[associated_enum(Warning)]
64#[warning(
65 code = "consecutive_jumps",
66 title = "consecutive jumps in hex pattern `{pattern_ident}`",
67)]
68#[label(
69 "these consecutive jumps will be treated as {coalesced_jump}",
70 coalesced_jump_loc
71)]
72pub struct ConsecutiveJumps {
73 report: Report,
74 pattern_ident: String,
75 coalesced_jump: String,
76 coalesced_jump_loc: CodeLoc,
77}
78
79impl ConsecutiveJumps {
80 /// Identifier of the pattern containing the consecutive jumps.
81 #[inline]
82 pub fn pattern(&self) -> &str {
83 self.pattern_ident.as_str()
84 }
85}
86
87/// A rule contains a loop that could be very slow.
88///
89/// This warning indicates that a rule contains a `for` loop that may be very
90/// slow because it iterates over a range with an upper bound that depends on
91/// `filesize`. For very large files this may mean hundreds of millions of
92/// iterations.
93///
94/// # Example
95///
96/// ```text
97/// warning[potentially_slow_loop]: potentially slow loop
98/// --> test.yar:1:34
99/// |
100/// 1 | rule t { condition: for any i in (0..filesize-1) : ( int32(i) == 0xcafebabe ) }
101/// | --------------- this range can be very large
102/// |
103/// ```
104#[derive(ErrorStruct, Debug, PartialEq, Eq)]
105#[associated_enum(Warning)]
106#[warning(
107 code = "potentially_slow_loop",
108 title = "potentially slow loop",
109)]
110#[label(
111 "this range can be very large",
112 loc
113)]
114pub struct PotentiallySlowLoop {
115 report: Report,
116 loc: CodeLoc,
117}
118
119/// A boolean expression may be impossible to match.
120///
121/// For instance, the condition `2 of ($a, $b) at 0` is impossible
122/// to match, unless that both `$a` and `$b` are the same pattern,
123/// or one is a prefix of the other. In most cases this expression
124/// is unsatisfiable because two different matches can match at the
125/// same file offset.
126///
127/// ## Example
128///
129/// ```text
130/// warning[unsatisfiable_expr]: potentially unsatisfiable expression
131/// --> line:6:5
132/// |
133/// 6 | 2 of ($*) at 0
134/// | - this implies that multiple patterns must match
135/// | ---- but they must match at the same offset
136/// |
137/// ```
138#[derive(ErrorStruct, Debug, PartialEq, Eq)]
139#[associated_enum(Warning)]
140#[warning(
141 code = "unsatisfiable_expr",
142 title = "potentially unsatisfiable expression"
143)]
144#[label(
145 "this implies that multiple patterns must match",
146 quantifier_loc
147)]
148#[label(
149 "but they must match at the same offset",
150 at_loc
151)]
152pub struct PotentiallyUnsatisfiableExpression {
153 report: Report,
154 quantifier_loc: CodeLoc,
155 at_loc: CodeLoc,
156}
157
158/// A boolean expression can't be satisfied.
159///
160/// ## Example
161///
162/// ```text
163/// warning[unsatisfiable_expr]: unsatisfiable expression
164/// --> test.yar:6:34
165/// |
166/// 6 | rule x { condition: "AD" == hash.sha256(0,filesize) }
167/// | ---- ------------------ this is a lowercase string
168/// | |
169/// | this contains uppercase characters
170/// |
171/// = note: a lowercase strings can't be equal to a string containing uppercase characters
172#[derive(ErrorStruct, Debug, PartialEq, Eq)]
173#[associated_enum(Warning)]
174#[warning(
175 code = "unsatisfiable_expr",
176 title = "unsatisfiable expression"
177)]
178#[label(
179 "{label_1}",
180 loc_1
181)]
182#[label(
183 "{label_2}",
184 loc_2
185)]
186#[footer(note)]
187pub struct UnsatisfiableExpression {
188 report: Report,
189 label_1: String,
190 label_2: String,
191 loc_1: CodeLoc,
192 loc_2: CodeLoc,
193 note: Option<String>,
194}
195
196
197/// A boolean expression always has the same value.
198///
199/// This warning indicates that some boolean expression is always true or false,
200/// regardless of the data being scanned.
201///
202/// ## Example
203///
204/// ```text
205/// warning[invariant_expr]: invariant boolean expression
206/// --> line:6:5
207/// |
208/// 6 | 3 of them
209/// | --------- this expression is always false
210/// |
211/// = note: the expression requires 3 matching patterns out of 2
212/// ```
213#[derive(ErrorStruct, Debug, PartialEq, Eq)]
214#[associated_enum(Warning)]
215#[warning(
216 code = "invariant_expr",
217 title = "invariant boolean expression"
218)]
219#[label(
220 "this expression is always {expr_value}",
221 expr_loc
222)]
223#[footer(note)]
224pub struct InvariantBooleanExpression {
225 report: Report,
226 expr_value: bool,
227 expr_loc: CodeLoc,
228 note: Option<String>,
229}
230
231/// A non-boolean expression is being used as a boolean.
232///
233/// ## Example
234///
235/// ```text
236/// warning[non_bool_expr]: non-boolean expression used as boolean
237/// --> line:3:14
238/// |
239/// 3 | condition: 2 and 3
240/// | - this expression is `integer` but is being used as `bool`
241/// |
242/// = note: non-zero integers are considered `true`, while zero is `false`
243/// ```
244#[derive(ErrorStruct, Debug, PartialEq, Eq)]
245#[associated_enum(Warning)]
246#[warning(
247 code = "non_bool_expr",
248 title = "non-boolean expression used as boolean"
249)]
250#[label(
251 "this expression is `{expr_type}` but is being used as `bool`",
252 expr_loc
253)]
254#[footer(note)]
255pub struct NonBooleanAsBoolean {
256 report: Report,
257 expr_type: String,
258 expr_loc: CodeLoc,
259 note: Option<String>,
260}
261
262/// Comparison between boolean and integer.
263///
264/// This warning indicates that some expression is a comparison between
265/// boolean and integer values.
266///
267/// ## Example
268///
269/// ```text
270/// warning[bool_int_comparison]: comparison between boolean and integer
271/// --> line:4:13
272/// |
273/// 4 | condition: test_proto2.array_bool[0] == 1
274/// | ------------------------------ this comparison can be replaced with: `test_proto2.array_bool[0]`
275/// |
276/// ```
277#[derive(ErrorStruct, Debug, PartialEq, Eq)]
278#[associated_enum(Warning)]
279#[warning(
280 code = "bool_int_comparison",
281 title = "comparison between boolean and integer"
282)]
283#[label(
284 "this is comparing an integer and a boolean",
285 expr_loc
286)]
287pub struct BooleanIntegerComparison {
288 report: Report,
289 expr_loc: CodeLoc,
290}
291
292/// Duplicate import statement.
293///
294/// This warning indicates that some module has been imported multiple times.
295///
296/// ## Example
297///
298/// ```text
299/// warning[duplicate_import]: duplicate import statement
300/// --> line:1:21
301/// |
302/// 1 | import "test_proto2"
303/// | -------------------- note: `test_proto2` imported here for the first time
304/// 2 | import "test_proto2"
305/// | -------------------- duplicate import
306/// |
307/// ```
308#[derive(ErrorStruct, Debug, PartialEq, Eq)]
309#[associated_enum(Warning)]
310#[warning(
311 code = "duplicate_import",
312 title = "duplicate import statement"
313)]
314#[label(
315 "duplicate import",
316 new_import_loc
317)]
318#[label(
319 "`{module_name}` imported here for the first time",
320 existing_import_loc,
321 Level::NOTE
322)]
323pub struct DuplicateImport {
324 report: Report,
325 module_name: String,
326 new_import_loc: CodeLoc,
327 existing_import_loc: CodeLoc,
328}
329
330
331/// Redundant case-insensitive modifier for a regular expression.
332///
333/// A regular expression can be made case-insensitive in two ways: by using the
334/// `nocase` modifier or by appending the `i` suffix to the pattern. Both
335/// methods achieve the same result, making it redundant to use them
336/// simultaneously.
337///
338/// For example, the following patterns are equivalent:
339///
340/// ```text
341/// $re = /some regexp/i
342/// $re = /some regexp/ nocase
343/// ```
344///
345/// ## Example
346///
347/// ```text
348/// warning[redundant_modifier]: redundant case-insensitive modifier
349/// --> line:3:15
350/// |
351/// 3 | $a = /foo/i nocase
352/// | - the `i` suffix indicates that the pattern is case-insensitive
353/// | ------ the `nocase` modifier does the same
354/// |
355/// ```
356#[derive(ErrorStruct, Debug, PartialEq, Eq)]
357#[associated_enum(Warning)]
358#[warning(
359 code = "redundant_modifier",
360 title = "redundant case-insensitive modifier"
361)]
362#[label(
363 "the `i` suffix indicates that the pattern is case-insensitive",
364 i_loc
365)]
366#[label(
367 "the `nocase` modifier does the same",
368 nocase_loc
369)]
370pub struct RedundantCaseModifier {
371 report: Report,
372 nocase_loc: CodeLoc,
373 i_loc: CodeLoc,
374}
375
376/// Some pattern may be potentially slow.
377///
378/// This warning indicates that a pattern may be very slow to match, and can
379/// degrade rule's the performance. In most cases this is caused by patterns
380/// that doesn't contain any large fixed sub-pattern that be used for speeding
381/// up the scan. For example, `{00 [1-10] 01}` is very slow because the only
382/// fixed sub-patterns (`00` and `01`) are only one byte long.
383///
384/// ## Example
385///
386/// ```text
387/// warning[slow_pattern]: slow pattern
388/// --> line:3:5
389/// |
390/// 3 | $a = {00 [1-10] 01}
391/// | ------------------ this pattern may slow down the scan
392/// |
393/// ```
394#[derive(ErrorStruct, Debug, PartialEq, Eq)]
395#[associated_enum(Warning)]
396#[warning(
397 code = "slow_pattern",
398 title = "slow pattern"
399)]
400#[label(
401 "this pattern may slow down the scan",
402 pattern_loc
403)]
404#[footer(note)]
405pub struct SlowPattern {
406 report: Report,
407 pattern_loc: CodeLoc,
408 note: Option<String>,
409}
410
411/// An unsupported module has been used.
412///
413/// If you use [`crate::Compiler::ignore_module`] for telling the compiler
414/// that some module is not supported, the compiler will raise this warning
415/// when the module is used in some of your rules.
416///
417/// ## Example
418///
419/// ```text
420/// warning[unsupported_module]: module `magic` is not supported
421/// --> line:4:5
422/// |
423/// 4 | magic.type()
424/// | ----- module `magic` used here
425/// |
426/// = note: the whole rule `foo` will be ignored
427/// ```
428#[derive(ErrorStruct, Debug, PartialEq, Eq)]
429#[associated_enum(Warning)]
430#[warning(
431 code = "unsupported_module",
432 title = "module `{module_name}` is not supported"
433)]
434#[label(
435 "module `{module_name}` used here",
436 module_name_loc
437)]
438#[footer(note)]
439pub struct IgnoredModule {
440 report: Report,
441 module_name: String,
442 module_name_loc: CodeLoc,
443 note: Option<String>,
444}
445
446/// A rule indirectly depends on some unsupported module.
447///
448/// If you use [`crate::Compiler::ignore_module`] for telling the compiler
449/// that some module is not supported, the compiler will raise this warning
450/// when a rule `A` uses some rule `B` that uses the module.
451///
452/// ## Example
453///
454/// ```text
455/// warning[ignored_rule]: rule `foo` will be ignored due to an indirect dependency on module `magic`
456/// --> line:9:5
457/// |
458/// 9 | bar
459/// | --- this other rule depends on module `magic`, which is unsupported
460/// |
461/// ```
462#[derive(ErrorStruct, Debug, PartialEq, Eq)]
463#[associated_enum(Warning)]
464#[warning(
465 code = "ignored_rule",
466 title = "rule `{ignored_rule}` will be ignored due to an indirect dependency on module `{module_name}`"
467)]
468#[label(
469 "this other rule depends on module `{module_name}`, which is unsupported",
470 ignored_rule_loc
471)]
472pub struct IgnoredRule {
473 report: Report,
474 module_name: String,
475 ignored_rule: String,
476 ignored_rule_loc: CodeLoc,
477}
478
479/// Some hex pattern can be written as a text literal.
480///
481/// For instance `{61 62 63}` can be written as "abc". Text literals are
482/// preferred over hex patterns because they are more legible.
483///
484/// ## Example
485///
486/// ```text
487/// warning[text_as_hex]: hex pattern could be written as text literal
488/// --> test.yar:6:4
489/// |
490/// 6 | $d = { 61 61 61 }
491/// | --------------- this pattern can be written as a text literal
492/// | --------------- help: replace with "aaa"
493/// ```
494#[derive(ErrorStruct, Debug, PartialEq, Eq)]
495#[associated_enum(Warning)]
496#[warning(
497 code = "text_as_hex",
498 title = "hex pattern could be written as text literal"
499)]
500#[label(
501 "this pattern can be written as a text literal",
502 pattern_loc
503)]
504pub struct TextPatternAsHex {
505 report: Report,
506 pattern_loc: CodeLoc,
507}
508
509/// Some metadata entry is invalid. This is only used if the compiler is
510/// configured to check for valid metadata (see: [`crate::linters::Metadata`]).
511///
512/// ## Example
513///
514/// ```text
515/// warning[invalid_metadata]: metadata `author` is not valid
516/// --> test.yar:4:5
517/// |
518/// 4 | author = 1234
519/// | ---- `author` must be a string
520/// |
521/// ```
522#[derive(ErrorStruct, Debug, PartialEq, Eq)]
523#[associated_enum(Warning)]
524#[warning(
525 code = "invalid_metadata",
526 title = "metadata `{name}` is not valid"
527)]
528#[label(
529 "{label}",
530 label_loc
531)]
532pub struct InvalidMetadata {
533 report: Report,
534 name: String,
535 label_loc: CodeLoc,
536 label: String,
537}
538
539/// Missing metadata. This is only used if the compiler is configured to check
540/// for required metadata (see: [`crate::linters::Metadata`]).
541///
542/// ## Example
543///
544/// ```text
545/// warning[missing_metadata]: required metadata is missing
546/// --> test.yar:12:6
547/// |
548/// 12 | rule pants {
549/// | ----- required metadata "date" not found
550/// |
551/// ```
552#[derive(ErrorStruct, Debug, PartialEq, Eq)]
553#[associated_enum(Warning)]
554#[warning(
555 code = "missing_metadata",
556 title = "required metadata is missing"
557)]
558#[label(
559 "required metadata `{name}` not found",
560 rule_loc
561)]
562#[footer(note)]
563pub struct MissingMetadata {
564 report: Report,
565 rule_loc: CodeLoc,
566 name: String,
567 note: Option<String>,
568}
569
570/// Rule name does not match regex. This is only used if the compiler is
571/// configured to check for it (see: [`crate::linters::RuleName`]).
572///
573/// ## Example
574///
575/// ```text
576/// warning[invalid_rule_name]: rule name does not match regex `APT_.*`
577/// --> test.yar:13:6
578/// |
579/// 13 | rule pants {
580/// | ----- this rule name does not match regex `APT_.*`
581/// |
582/// ```
583#[derive(ErrorStruct, Debug, PartialEq, Eq)]
584#[associated_enum(Warning)]
585#[warning(
586 code = "invalid_rule_name",
587 title = "rule name does not match regex `{regex}`"
588)]
589#[label(
590 "this rule name does not match regex `{regex}`",
591 rule_loc
592)]
593pub struct InvalidRuleName {
594 report: Report,
595 rule_loc: CodeLoc,
596 regex: String,
597}
598
599/// A loop or nested loops have a total number of iterations exceeding a
600/// predefined threshold.
601///
602/// This warning indicates that a rule contains a `for` loop, or a set of nested
603/// `for` loops, that may be very slow because the total number of iterations
604/// is very large.
605///
606/// # Example
607///
608/// ```text
609/// warning[too_many_iterations]: loop has too many iterations
610/// --> test.yar:1:20
611/// |
612/// 1 | rule t { condition: for any i in (0..1000) : ( for any j in (0..1000) : ( true ) ) }
613/// | -------------------------------------------------------------- this loop iterates 1000000 times, which may be slow
614/// |
615/// ```
616#[derive(ErrorStruct, Debug, PartialEq, Eq)]
617#[associated_enum(Warning)]
618#[warning(
619 code = "too_many_iterations",
620 title = "loop has too many iterations",
621)]
622#[label(
623 "this loop iterates {iterations} times, which may be slow",
624 loc
625)]
626pub struct TooManyIterations {
627 report: Report,
628 iterations: i64,
629 loc: CodeLoc,
630}
631
632/// Unknown tag. This is only used if the compiler is configured to check
633/// for required tags (see: [`crate::linters::Tags`]).
634///
635/// ## Example
636///
637/// ```text
638/// warning[unknown_tag]: tag not in allowed list
639/// --> rules/test.yara:1:10
640/// |
641/// 1 | rule a : foo {
642/// | --- tag `foo` not in allowed list
643/// |
644/// = note: allowed tags: test, bar
645/// ```
646#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)]
647#[associated_enum(Warning)]
648#[warning(
649 code = "unknown_tag",
650 title = "tag not in allowed list"
651)]
652#[label(
653 "tag `{name}` not in allowed list",
654 tag_loc
655)]
656#[footer(note)]
657pub struct UnknownTag {
658 report: Report,
659 tag_loc: CodeLoc,
660 name: String,
661 note: Option<String>,
662}
663
664/// Tag does not match regex. This is only used if the compiler is configured to
665/// check for it (see: [`crate::linters::Tags`]).
666///
667/// ## Example
668///
669/// ```text
670/// warning[invalid_tag]: tag `foo` does not match regex `bar`
671/// --> rules/test.yara:1:10
672/// |
673/// 1 | rule a : foo {
674/// | --- tag `foo` does not match regex `bar`
675/// |
676/// ```
677#[derive(ErrorStruct, Debug, PartialEq, Eq)]
678#[associated_enum(Warning)]
679#[warning(
680 code = "invalid_tag",
681 title = "tag `{name}` does not match regex `{regex}`"
682)]
683#[label(
684 "tag `{name}` does not match regex `{regex}`",
685 tag_loc
686)]
687pub struct InvalidTag {
688 report: Report,
689 tag_loc: CodeLoc,
690 name: String,
691 regex: String,
692}
693
694/// A deprecated field was used in a YARA rule.
695///
696/// ## Example
697///
698/// ```text
699/// warning[deprecated_field]: field `foo` is deprecated
700/// --> rules/test.yara:1:10
701/// |
702/// 3 | vt.metadata.foo
703/// | --- `foo` is deprecated, use `bar` instead
704/// |
705/// ```
706#[derive(ErrorStruct, Debug, PartialEq, Eq)]
707#[associated_enum(Warning)]
708#[warning(
709 code = "deprecated_field",
710 title = "field `{name}` is deprecated"
711)]
712#[label(
713 "{msg}",
714 loc
715)]
716pub struct DeprecatedField {
717 report: Report,
718 name: String,
719 loc: CodeLoc,
720 msg: String,
721}
722
723/// An ambiguous expression is used in a condition.
724///
725/// ## Example
726///
727/// ```text
728/// warning[ambiguous_expr]: ambiguous expression
729/// --> line:6:5
730/// |
731/// 6 | 0 of them
732/// | --------- this expression is ambiguous
733/// |
734/// help: consider using `none` instead of `0`
735/// |
736/// 6 - 0 of them
737/// 6 + none of them
738/// |
739/// ```
740#[derive(ErrorStruct, Debug, PartialEq, Eq)]
741#[associated_enum(Warning)]
742#[warning(
743 code = "ambiguous_expr",
744 title = "ambiguous expression"
745)]
746#[label(
747 "this expression is ambiguous",
748 loc
749)]
750pub struct AmbiguousExpression {
751 report: Report,
752 loc: CodeLoc,
753}
754
755/// An identifier was declared but not used.
756///
757/// ## Example
758///
759/// ```text
760/// warning[unused_identifier]: unused identifier
761/// --> test.yar:6:32
762/// |
763/// 6 | with a = 1, b = 2, c = 3 : (
764/// | - this identifier is unused
765/// ```
766#[derive(ErrorStruct, Debug, PartialEq, Eq)]
767#[associated_enum(Warning)]
768#[warning(
769 code = "unused_identifier",
770 title = "unused identifier",
771)]
772#[label(
773 "this identifier declared but not used",
774 loc
775)]
776pub struct UnusedIdentifier {
777 report: Report,
778 loc: CodeLoc,
779}
780
781
782/// A global rule has been used as part of a rule condition.
783///
784/// Referencing a global rule within a condition is redundant and may create
785/// logical contradictions. Global rules are implicit prerequisites for all
786/// non-global rules. Therefore, explicitly checking a global rule in a
787/// condition is unnecessary. Furthermore, negating a global rule renders the
788/// condition unsatisfiable: the condition requires the global rule to be false,
789/// but a false global rule prevents all non-global rules from being true.
790///
791/// ## Example
792///
793/// ```text
794/// warning[global_rule_misuse]: global rule used in condition
795/// --> line:7:5
796/// |
797/// 7 | test_1
798/// | ------ a global rule is being used as part of an condition
799/// |
800/// = note: referencing a global rule in a condition is redundant, and may result in an unsatisfiable condition
801/// ```
802#[derive(ErrorStruct, Debug, PartialEq, Eq)]
803#[associated_enum(Warning)]
804#[warning(
805 code = "global_rule_misuse",
806 title = "global rule used in condition",
807)]
808#[label(
809 "a global rule is being used as part of an condition",
810 loc
811)]
812#[footer(note)]
813pub struct GlobalRuleMisuse {
814 report: Report,
815 loc: CodeLoc,
816 note: Option<String>,
817}