Skip to content

Commit

Permalink
feat(capi+go+py): implement APIs for exposing rule tags.
Browse files Browse the repository at this point in the history
  • Loading branch information
plusvic committed Oct 28, 2024
1 parent 94abb1a commit 6e044b0
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 158 deletions.
24 changes: 24 additions & 0 deletions capi/include/yara_x.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ typedef void (*YRX_METADATA_CALLBACK)(const struct YRX_METADATA *metadata,
typedef void (*YRX_PATTERN_CALLBACK)(const struct YRX_PATTERN *pattern,
void *user_data);

// Callback function passed to [`yrx_rule_iter_tags`].
//
// The callback is called for each tag defined in the rule, and it receives
// a pointer to a string with the tag name. This pointer is guaranteed to be
// valid while the callback function is being executed, but it will be freed
// after the callback function returns, so you cannot use this pointer, or
// any other pointer contained in the structure, outside the callback.
//
// The callback also receives a `user_data` pointer that can point to arbitrary
// data owned by the user.
typedef void (*YRX_TAG_CALLBACK)(const char *tag,
void *user_data);

// Callback function passed to [`yrx_scanner_on_matching_rule`] or
// [`yrx_rules_iter`].
//
Expand Down Expand Up @@ -458,6 +471,17 @@ enum YRX_RESULT yrx_rule_iter_patterns(const struct YRX_RULE *rule,
YRX_PATTERN_CALLBACK callback,
void *user_data);

// Iterates over the tags in a rule, calling the callback with a pointer
// to each tag.
//
// The `user_data` pointer can be used to provide additional context to your
// callback function.
//
// See [`YRX_TAG_CALLBACK`] for more details.
enum YRX_RESULT yrx_rule_iter_tags(const struct YRX_RULE *rule,
YRX_TAG_CALLBACK callback,
void *user_data);

// Iterates over the compiled rules, calling the callback function for each
// rule.
//
Expand Down
42 changes: 41 additions & 1 deletion capi/src/rule.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ffi::{c_void, CString};
use std::ffi::{c_char, c_void, CString};
use yara_x::MetaValue;

use crate::{
Expand Down Expand Up @@ -179,3 +179,43 @@ pub unsafe extern "C" fn yrx_rule_iter_patterns(

YRX_RESULT::SUCCESS
}

/// Callback function passed to [`yrx_rule_iter_tags`].
///
/// The callback is called for each tag defined in the rule, and it receives
/// a pointer to a string with the tag name. This pointer is guaranteed to be
/// valid while the callback function is being executed, but it will be freed
/// after the callback function returns, so you cannot use this pointer, or
/// any other pointer contained in the structure, outside the callback.
///
/// The callback also receives a `user_data` pointer that can point to arbitrary
/// data owned by the user.
pub type YRX_TAG_CALLBACK =
extern "C" fn(tag: *const c_char, user_data: *mut c_void) -> ();

/// Iterates over the tags in a rule, calling the callback with a pointer
/// to each tag.
///
/// The `user_data` pointer can be used to provide additional context to your
/// callback function.
///
/// See [`YRX_TAG_CALLBACK`] for more details.
#[no_mangle]
pub unsafe extern "C" fn yrx_rule_iter_tags(
rule: *const YRX_RULE,
callback: YRX_TAG_CALLBACK,
user_data: *mut c_void,
) -> YRX_RESULT {
let tags_iter = if let Some(rule) = rule.as_ref() {
rule.0.tags()
} else {
return YRX_RESULT::INVALID_ARGUMENT;
};

for tag in tags_iter {
let tag_name = CString::new(tag.identifier()).unwrap();
callback(tag_name.as_ptr(), user_data)
}

YRX_RESULT::SUCCESS
}
33 changes: 24 additions & 9 deletions capi/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use crate::compiler::{
};
use crate::{
yrx_buffer_destroy, yrx_last_error, yrx_rule_identifier,
yrx_rule_iter_metadata, yrx_rule_iter_patterns, yrx_rule_namespace,
yrx_rules_deserialize, yrx_rules_destroy, yrx_rules_iter,
yrx_rules_iter_imports, yrx_rules_serialize, yrx_scanner_create,
yrx_scanner_destroy, yrx_scanner_on_matching_rule, yrx_scanner_scan,
yrx_scanner_set_global_bool, yrx_scanner_set_global_float,
yrx_scanner_set_global_int, yrx_scanner_set_global_str,
yrx_scanner_set_timeout, YRX_BUFFER, YRX_METADATA, YRX_PATTERN,
YRX_RESULT, YRX_RULE,
yrx_rule_iter_metadata, yrx_rule_iter_patterns, yrx_rule_iter_tags,
yrx_rule_namespace, yrx_rules_deserialize, yrx_rules_destroy,
yrx_rules_iter, yrx_rules_iter_imports, yrx_rules_serialize,
yrx_scanner_create, yrx_scanner_destroy, yrx_scanner_on_matching_rule,
yrx_scanner_scan, yrx_scanner_set_global_bool,
yrx_scanner_set_global_float, yrx_scanner_set_global_int,
yrx_scanner_set_global_str, yrx_scanner_set_timeout, YRX_BUFFER,
YRX_METADATA, YRX_PATTERN, YRX_RESULT, YRX_RULE,
};

use std::ffi::{c_char, c_void, CStr, CString};
Expand Down Expand Up @@ -49,6 +49,12 @@ extern "C" fn on_pattern_iter(
*count += 1;
}

extern "C" fn on_tag_iter(_tag: *const c_char, user_data: *mut c_void) {
let ptr = user_data as *mut i32;
let count = unsafe { ptr.as_mut().unwrap() };
*count += 1;
}

extern "C" fn on_rule_match(rule: *const YRX_RULE, user_data: *mut c_void) {
let mut ptr = std::ptr::null();
let mut len = 0;
Expand All @@ -74,6 +80,15 @@ extern "C" fn on_rule_match(rule: *const YRX_RULE, user_data: *mut c_void) {
);
// The rule has one pattern.
assert_eq!(count, 1);

let mut count = 0;
yrx_rule_iter_tags(
rule,
on_tag_iter,
&mut count as *mut i32 as *mut c_void,
);
// The rule has two tags.
assert_eq!(count, 2);
}

let ptr = user_data as *mut i32;
Expand All @@ -93,7 +108,7 @@ fn capi() {
let src = CString::new(
br#"
import "pe"
rule test {
rule test : tag1 tag2 {
meta:
some_int = 1
some_string = "foo"
Expand Down
5 changes: 4 additions & 1 deletion go/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func TestRules(t *testing.T) {
c, err := NewCompiler()
assert.NoError(t, err)

c.AddSource(`rule test_1 {
c.AddSource(`rule test_1 : tag1 tag2 {
condition:
true
}`)
Expand Down Expand Up @@ -243,6 +243,9 @@ func TestRules(t *testing.T) {
assert.Equal(t, "default", slice[0].Namespace())
assert.Equal(t, "default", slice[1].Namespace())

assert.Equal(t, []string{"tag1", "tag2"}, slice[0].Tags())
assert.Equal(t, []string{}, slice[1].Tags())

assert.Len(t, slice[0].Metadata(), 0)
assert.Len(t, slice[1].Metadata(), 4)

Expand Down
46 changes: 43 additions & 3 deletions go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ package yara_x
// return yrx_rules_iter_imports(rules, callback, (void*) imports_handle);
// }
//
// enum YRX_RESULT static inline _yrx_rule_iter_tags(
// const struct YRX_RULE *rule,
// YRX_TAG_CALLBACK callback,
// uintptr_t tags_handle)
// {
// return yrx_rule_iter_tags(rule, callback, (void*) tags_handle);
// }
//
// enum YRX_RESULT static inline _yrx_rule_iter_metadata(
// const struct YRX_RULE *rule,
// YRX_METADATA_CALLBACK callback,
Expand Down Expand Up @@ -70,6 +78,7 @@ package yara_x
// extern void metadataCallback(YRX_METADATA*, uintptr_t);
// extern void patternCallback(YRX_PATTERN*, uintptr_t);
// extern void matchCallback(YRX_MATCH*, uintptr_t);
// extern void tagCallback(char*, uintptr_t);
//
import "C"

Expand Down Expand Up @@ -201,6 +210,7 @@ func (r *Rules) Destroy() {

// This is the callback called by yrx_rules_iterate, when Rules.GetRules is
// called.
//
//export onRule
func onRule(rule *C.YRX_RULE, handle C.uintptr_t) {
h := cgo.Handle(handle)
Expand Down Expand Up @@ -255,6 +265,7 @@ func (r *Rules) Imports() []string {
type Rule struct {
namespace string
identifier string
tags []string
patterns []Pattern
metadata []Metadata
}
Expand Down Expand Up @@ -295,6 +306,17 @@ func newRule(cRule *C.YRX_RULE) *Rule {

identifier := C.GoStringN((*C.char)(unsafe.Pointer(str)), C.int(len))

tags := make([]string, 0)
tagsHandle := cgo.NewHandle(&tags)
defer tagsHandle.Delete()

if C._yrx_rule_iter_tags(
cRule,
C.YRX_TAG_CALLBACK(C.tagCallback),
C.uintptr_t(tagsHandle)) != C.SUCCESS {
panic("yrx_rule_iter_tags failed")
}

metadata := make([]Metadata, 0)
metadataHandle := cgo.NewHandle(&metadata)
defer metadataHandle.Delete()
Expand All @@ -320,6 +342,7 @@ func newRule(cRule *C.YRX_RULE) *Rule {
rule := &Rule{
namespace,
identifier,
tags,
patterns,
metadata,
}
Expand All @@ -337,6 +360,11 @@ func (r *Rule) Namespace() string {
return r.namespace
}

// Tags returns the rule's tags.
func (r *Rule) Tags() []string {
return r.tags
}

// Identifier associated to the metadata.
func (m *Metadata) Identifier() string {
return m.identifier
Expand Down Expand Up @@ -403,7 +431,19 @@ func importCallback(moduleName *C.char, handle C.uintptr_t) {
*imports = append(*imports, C.GoString(moduleName))
}

// This is the callback called by yrx_rules_iter_patterns
// This is the callback called by yrx_rule_iter_tags
//
//export tagCallback
func tagCallback(tag *C.char, handle C.uintptr_t) {
h := cgo.Handle(handle)
tags, ok := h.Value().(*[]string)
if !ok {
panic("tagsCallback didn't receive a *[]string")
}
*tags = append(*tags, C.GoString(tag))
}

// This is the callback called by yrx_rule_iter_patterns
//
//export patternCallback
func patternCallback(pattern *C.YRX_PATTERN, handle C.uintptr_t) {
Expand Down Expand Up @@ -437,7 +477,7 @@ func patternCallback(pattern *C.YRX_PATTERN, handle C.uintptr_t) {
})
}

// This is the callback called by yrx_rules_iter_patterns
// This is the callback called by yrx_rule_iter_metadata
//
//export metadataCallback
func metadataCallback(metadata *C.YRX_METADATA, handle C.uintptr_t) {
Expand Down Expand Up @@ -472,7 +512,7 @@ func metadataCallback(metadata *C.YRX_METADATA, handle C.uintptr_t) {
})
}

// This is the callback called by yrx_rules_iter_patterns
// This is the callback called by yrx_pattern_iter_matches
//
//export matchCallback
func matchCallback(match *C.YRX_MATCH, handle C.uintptr_t) {
Expand Down
2 changes: 1 addition & 1 deletion go/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,4 @@ func BenchmarkScan(b *testing.B) {
_ = rule.Identifier()
}
}
}
}
12 changes: 12 additions & 0 deletions py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ impl ScanResults {
struct Rule {
identifier: String,
namespace: String,
tags: Py<PyTuple>,
metadata: Py<PyTuple>,
patterns: Py<PyTuple>,
}
Expand All @@ -415,6 +416,12 @@ impl Rule {
self.namespace.as_str()
}

/// Returns the rule's tags.
#[getter]
fn tags(&self) -> Py<PyTuple> {
Python::with_gil(|py| self.tags.clone_ref(py))
}

/// A tuple of pairs `(identifier, value)` with the metadata associated to
/// the rule.
#[getter]
Expand Down Expand Up @@ -585,6 +592,11 @@ fn rule_to_py(py: Python, rule: yrx::Rule) -> PyResult<Py<Rule>> {
Rule {
identifier: rule.identifier().to_string(),
namespace: rule.namespace().to_string(),
tags: PyTuple::new_bound(
py,
rule.tags().map(|tag| tag.identifier()),
)
.unbind(),
metadata: PyTuple::new_bound(
py,
rule.metadata()
Expand Down
13 changes: 13 additions & 0 deletions py/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@ def test_metadata():
)


def test_tags():
rules = yara_x.compile('''
rule test : tag1 tag2 {
condition:
true
}
''')

matching_rules = rules.scan(b'').matching_rules

assert matching_rules[0].tags == ("tag1", "tag2")


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
Loading

0 comments on commit 6e044b0

Please sign in to comment.