Skip to content

Commit

Permalink
feat: implement API for treating slow patterns as error instead of wa…
Browse files Browse the repository at this point in the history
…rnings (#131)

Slow patterns are those that have atoms with less than 2 bytes. Such patterns can slow down scanning and the compiler raises a warning when it finds one of those patterns. The new APIs allow telling the compiler to raise an error instead of a warning.
  • Loading branch information
plusvic authored May 28, 2024
1 parent e8dedd7 commit 8c96849
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 7 deletions.
4 changes: 4 additions & 0 deletions capi/include/yara_x.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
// constructs that YARA-X doesn't accept by default.
#define YRX_RELAXED_RE_SYNTAX 2

// Flag passed to [`yrx_compiler_create`] for treating slow patterns as
// errors instead of warnings.
#define YRX_ERROR_ON_SLOW_PATTERN 4

// Metadata value types.
typedef enum YRX_METADATA_VALUE_TYPE {
I64,
Expand Down
7 changes: 7 additions & 0 deletions capi/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub const YRX_COLORIZE_ERRORS: u32 = 1;
/// constructs that YARA-X doesn't accept by default.
pub const YRX_RELAXED_RE_SYNTAX: u32 = 2;

/// Flag passed to [`yrx_compiler_create`] for treating slow patterns as
/// errors instead of warnings.
pub const YRX_ERROR_ON_SLOW_PATTERN: u32 = 4;

fn _yrx_compiler_create<'a>(flags: u32) -> yara_x::Compiler<'a> {
let mut compiler = yara_x::Compiler::new();
if flags & YRX_RELAXED_RE_SYNTAX != 0 {
Expand All @@ -35,6 +39,9 @@ fn _yrx_compiler_create<'a>(flags: u32) -> yara_x::Compiler<'a> {
if flags & YRX_COLORIZE_ERRORS != 0 {
compiler.colorize_errors(true);
}
if flags & YRX_ERROR_ON_SLOW_PATTERN != 0 {
compiler.error_on_slow_pattern(true);
}
compiler
}

Expand Down
14 changes: 14 additions & 0 deletions go/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,20 @@ func RelaxedReSyntax(yes bool) CompileOption {
}
}

// ErrorOnSlowPattern is an option for [NewCompiler] and [Compile] that
// tells the compiler to treat slow patterns as errors instead of warnings.
func ErrorOnSlowPattern(yes bool) CompileOption {
return func(c *Compiler) error {
c.errorOnSlowPattern = yes
return nil
}
}

// Compiler represent a YARA compiler.
type Compiler struct {
cCompiler *C.YRX_COMPILER
relaxedReSyntax bool
errorOnSlowPattern bool
ignoredModules map[string]bool
vars map[string]interface{}
}
Expand All @@ -97,6 +107,10 @@ func NewCompiler(opts... CompileOption) (*Compiler, error) {
flags |= C.YRX_RELAXED_RE_SYNTAX
}

if c.errorOnSlowPattern {
flags |= C.YRX_ERROR_ON_SLOW_PATTERN
}

C.yrx_compiler_create(flags, &c.cCompiler)

if err := c.initialize(); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions go/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ func TestRelaxedReSyntax(t *testing.T) {
assert.Len(t, matchingRules, 1)
}


func TestErrorOnSlowPattern(t *testing.T) {
_, err := Compile(`
rule test { strings: $a = /a.*b/ condition: $a }`,
ErrorOnSlowPattern(true))
assert.Error(t, err)
}

func TestSerialization(t *testing.T) {
r, err := Compile("rule test { condition: true }")
assert.NoError(t, err)
Expand Down
4 changes: 4 additions & 0 deletions lib/src/compiler/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,8 @@ pub enum CompileError {
span: Span,
note: Option<String>,
},

#[error("slow pattern")]
#[label("this pattern may slow down the scan", span)]
SlowPattern { detailed_report: String, span: Span },
}
24 changes: 22 additions & 2 deletions lib/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ pub struct Compiler<'a> {
/// escape sequences.
relaxed_re_syntax: bool,

/// If true, slow patterns produce an error instead of a warning. A slow
/// pattern is one with atoms shorter than 2 bytes.
error_on_slow_pattern: bool,

/// Used for generating error and warning reports.
report_builder: ReportBuilder,

Expand Down Expand Up @@ -310,6 +314,7 @@ impl<'a> Compiler<'a> {
wasm_symbols,
wasm_exports,
relaxed_re_syntax: false,
error_on_slow_pattern: false,
next_pattern_id: PatternId(0),
current_pattern_id: PatternId(0),
current_namespace: default_namespace,
Expand Down Expand Up @@ -590,6 +595,14 @@ impl<'a> Compiler<'a> {
self
}

/// When enabled, slow patterns produce an error instead of a warning.
///
/// This is disabled by default.
pub fn error_on_slow_pattern(&mut self, yes: bool) -> &mut Self {
self.error_on_slow_pattern = yes;
self
}

/// Returns the warnings emitted by the compiler.
#[inline]
pub fn warnings(&self) -> &[Warning] {
Expand Down Expand Up @@ -1597,8 +1610,15 @@ impl<'a> Compiler<'a> {
}

if slow_pattern {
self.warnings
.add(|| Warning::slow_pattern(&self.report_builder, span));
if self.error_on_slow_pattern {
return Err(Box::new(CompileError::slow_pattern(
&self.report_builder,
span,
)));
} else {
self.warnings
.add(|| Warning::slow_pattern(&self.report_builder, span));
}
}

Ok((atoms, is_fast_regexp))
Expand Down
27 changes: 22 additions & 5 deletions py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@ fn compile(src: &str) -> PyResult<Rules> {
struct Compiler {
inner: yrx::Compiler<'static>,
relaxed_re_syntax: bool,
error_on_slow_pattern: bool,
}

impl Compiler {
fn new_inner(relaxed_re_syntax: bool) -> yrx::Compiler<'static> {
fn new_inner(
relaxed_re_syntax: bool,
error_on_slow_pattern: bool,
) -> yrx::Compiler<'static> {
let mut compiler = yrx::Compiler::new();
if relaxed_re_syntax {
compiler.relaxed_re_syntax(true);
}
if error_on_slow_pattern {
compiler.error_on_slow_pattern(true);
}
compiler
}
}
Expand All @@ -77,10 +84,17 @@ impl Compiler {
/// their meaning from the context (e.g., `{` and `}` in `/foo{}bar/` are
/// literal, but in `/foo{0,1}bar/` they form the repetition operator
/// `{0,1}`).
///
/// The `error_on_slow_pattern` argument tells the compiler to treat slow
/// patterns as errors, instead of warnings.
#[new]
#[pyo3(signature = (*, relaxed_re_syntax=false))]
fn new(relaxed_re_syntax: bool) -> Self {
Self { inner: Self::new_inner(relaxed_re_syntax), relaxed_re_syntax }
#[pyo3(signature = (*, relaxed_re_syntax=false, error_on_slow_pattern=false))]
fn new(relaxed_re_syntax: bool, error_on_slow_pattern: bool) -> Self {
Self {
inner: Self::new_inner(relaxed_re_syntax, error_on_slow_pattern),
relaxed_re_syntax,
error_on_slow_pattern,
}
}

/// Adds a YARA source code to be compiled.
Expand Down Expand Up @@ -160,7 +174,10 @@ impl Compiler {
fn build(&mut self) -> Rules {
let compiler = mem::replace(
&mut self.inner,
Self::new_inner(self.relaxed_re_syntax),
Self::new_inner(
self.relaxed_re_syntax,
self.error_on_slow_pattern,
),
);
Rules::new(compiler.build())
}
Expand Down
6 changes: 6 additions & 0 deletions py/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ def test_relaxed_re_syntax():
assert len(matching_rules) == 1


def test_error_on_slow_pattern():
compiler = yara_x.Compiler(error_on_slow_pattern=True)
with pytest.raises(yara_x.CompileError):
compiler.add_source(r'rule test {strings: $a = /a.*b/ condition: $a}')


def test_int_globals():
compiler = yara_x.Compiler()
compiler.define_global('some_int', 1)
Expand Down

0 comments on commit 8c96849

Please sign in to comment.