Skip to content

Commit

Permalink
feat: implement the API for unsupported modules in Go and Python
Browse files Browse the repository at this point in the history
  • Loading branch information
plusvic committed Mar 15, 2024
1 parent 7fcdf63 commit 734d86a
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 7 deletions.
9 changes: 9 additions & 0 deletions capi/include/yara-x.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ void yrx_compiler_destroy(struct YRX_COMPILER *compiler);
enum YRX_RESULT yrx_compiler_add_source(struct YRX_COMPILER *compiler,
const char *src);

// Tell the compiler that a YARA module is not supported.
//
// Import statements for unsupported modules will be ignored without
// errors, but a warning will be used. Any rule that make use of an
// unsupported module will be ignored, while the rest of rules that
// don't rely on that module will be correctly compiled.
enum YRX_RESULT yrx_compiler_add_unsupported_module(struct YRX_COMPILER *compiler,
const char *module);

// Creates a new namespace.
//
// Further calls to `yrx_compiler_add_source` will put the rules under the
Expand Down
28 changes: 28 additions & 0 deletions capi/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,34 @@ pub unsafe extern "C" fn yrx_compiler_add_source(
}
}

/// Tell the compiler that a YARA module is not supported.
///
/// Import statements for unsupported modules will be ignored without
/// errors, but a warning will be used. Any rule that make use of an
/// unsupported module will be ignored, while the rest of rules that
/// don't rely on that module will be correctly compiled.
#[no_mangle]
pub unsafe extern "C" fn yrx_compiler_add_unsupported_module(
compiler: *mut YRX_COMPILER,
module: *const c_char,
) -> YRX_RESULT {
let compiler = if let Some(compiler) = compiler.as_mut() {
compiler
} else {
return YRX_RESULT::INVALID_ARGUMENT;
};

let module = if let Ok(module) = CStr::from_ptr(module).to_str() {
module
} else {
return YRX_RESULT::INVALID_ARGUMENT;
};

compiler.inner.add_unsupported_module(module);

YRX_RESULT::SUCCESS
}

/// Creates a new namespace.
///
/// Further calls to `yrx_compiler_add_source` will put the rules under the
Expand Down
10 changes: 10 additions & 0 deletions go/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ func (c *Compiler) AddSource(src string) error {
return nil
}

func (c *Compiler) AddUnsupportedModule(module string) {
cModule := C.CString(module)
defer C.free(unsafe.Pointer(cModule))
result := C.yrx_compiler_add_unsupported_module(c.cCompiler, cModule)
if result != C.SUCCESS {
panic("yrx_compiler_add_unsupported_module failed")
}
runtime.KeepAlive(c)
}

func (c *Compiler) NewNamespace(namespace string) {
cNamespace := C.CString(namespace)
defer C.free(unsafe.Pointer(cNamespace))
Expand Down
14 changes: 14 additions & 0 deletions go/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ func TestNamespaces(t *testing.T) {
assert.Len(t, matchingRules, 2)
}

func TestUnsupportedModules(t *testing.T) {
c := NewCompiler()
c.AddUnsupportedModule("unsupported_module")
c.NewNamespace("foo")
c.AddSource(`
import "unsupported_module"
rule test { condition: true }`)

s := NewScanner(c.Build())
matchingRules, _ := s.Scan([]byte{})

assert.Len(t, matchingRules, 1)
}

func TestSerialization(t *testing.T) {
c := NewCompiler()
c.AddSource("rule test { condition: true }")
Expand Down
10 changes: 10 additions & 0 deletions py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ impl Compiler {
self.inner.new_namespace(namespace);
}

/// Tell the compiler that a YARA module is not supported.
///
/// Import statements for unsupported modules will be ignored without
/// errors, but a warning will be used. Any rule that make use of an
/// unsupported module will be ignored, while the rest of rules that
/// don't rely on that module will be correctly compiled.
fn add_unsupported_module(&mut self, module: &str) {
self.inner.add_unsupported_module(module);
}

/// Builds the source code previously added to the compiler.
///
/// This function returns an instance of [`Rules`] containing all the rules
Expand Down
28 changes: 21 additions & 7 deletions py/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ def test_namespaces():
compiler.add_source('rule bar {strings: $bar = "bar" condition: $bar}')
rules = compiler.build()
matching_rules = rules.scan(b'foobar').matching_rules

assert len(matching_rules) == 2

assert matching_rules[0].identifier == 'foo'
assert matching_rules[0].namespace == 'foo'
assert len(matching_rules[0].patterns) == 1
Expand All @@ -100,6 +100,7 @@ def test_namespaces():
assert matching_rules[1].patterns[0].matches[0].length == 3
assert matching_rules[1].patterns[0].matches[0].xor_key is None


def test_compile_and_scan():
rules = yara_x.compile('rule foo {strings: $a = "foo" condition: $a}')
matching_rules = rules.scan(b'foobar').matching_rules
Expand Down Expand Up @@ -134,7 +135,8 @@ def test_xor_key():

def test_scanner_timeout():
compiler = yara_x.Compiler()
compiler.add_source('rule foo {condition: for all i in (0..10000000000) : ( true )}')
compiler.add_source(
'rule foo {condition: for all i in (0..10000000000) : ( true )}')
scanner = yara_x.Scanner(compiler.build())
scanner.timeout(1)
with pytest.raises(Exception, match='timeout'):
Expand All @@ -147,24 +149,36 @@ def test_module_outputs():
assert module_outputs['test_proto2']['int32One'] == 1


def test_unsupported_modules():
compiler = yara_x.Compiler()
compiler.add_unsupported_module("unsupported_module")
compiler.add_source(
'import "unsupported_module" rule foo {condition: true}')
rules = compiler.build()
assert len(rules.scan(b'').matching_rules) == 1


def test_serialization():
rules = yara_x.compile('rule foo {condition: true}')
f = io.BytesIO()
rules.serialize_into(f)
f.seek(0)
rules = yara_x.Rules.deserialize_from(f)
assert len(rules.scan(b'').matching_rules) == 1


def test_console_log():
ok = False
ok = False

def callback(msg):
nonlocal ok
if msg == 'foo':
ok = True

compiler = yara_x.Compiler()
compiler.add_source('import "console" rule foo {condition: console.log("foo")}')
compiler.add_source(
'import "console" rule foo {condition: console.log("foo")}')
scanner = yara_x.Scanner(compiler.build())
scanner.console_log(callback)
scanner.scan(b'')
assert ok
assert ok

0 comments on commit 734d86a

Please sign in to comment.