Skip to content

Commit ccee38a

Browse files
committed
feat: implement APIs in C and Golang for accessing the individual rules contained in a Rules object.
Also simplifies the logic for passing arguments to C callback functions from Golang.
1 parent 0398fcb commit ccee38a

File tree

7 files changed

+168
-80
lines changed

7 files changed

+168
-80
lines changed

capi/include/yara_x.h

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ typedef struct YRX_BUFFER {
9292
size_t length;
9393
} YRX_BUFFER;
9494

95+
// Callback function passed to [`yrx_scanner_on_matching_rule`] or
96+
// [`yrx_rules_iterate`].
97+
//
98+
// The callback receives a pointer to a rule, represented by a [`YRX_RULE`]
99+
// structure. This pointer is guaranteed to be valid while the callback
100+
// function is being executed, but it may be freed after the callback function
101+
// returns, so you cannot use the pointer outside the callback.
102+
//
103+
// It also receives the `user_data` pointer that can point to arbitrary data
104+
// owned by the user.
105+
typedef void (*YRX_RULE_CALLBACK)(const struct YRX_RULE *rule,
106+
void *user_data);
107+
95108
// Represents a metadata value that contains raw bytes.
96109
typedef struct YRX_METADATA_BYTES {
97110
// Number of bytes.
@@ -160,20 +173,6 @@ typedef struct YRX_PATTERNS {
160173
struct YRX_PATTERN *patterns;
161174
} YRX_PATTERNS;
162175

163-
// Callback function passed to the scanner via [`yrx_scanner_on_matching_rule`]
164-
// which receives notifications about matching rules.
165-
//
166-
// The callback receives a pointer to the matching rule, represented by a
167-
// [`YRX_RULE`] structure. This pointer is guaranteed to be valid while the
168-
// callback function is being executed, but it may be freed after the callback
169-
// function returns, so you cannot use the pointer outside the callback.
170-
//
171-
// It also receives the `user_data` pointer that was passed to the
172-
// [`yrx_scanner_on_matching_rule`] function, which can point to arbitrary
173-
// data owned by the user.
174-
typedef void (*YRX_ON_MATCHING_RULE)(const struct YRX_RULE *rule,
175-
void *user_data);
176-
177176
// Compiles YARA source code and creates a [`YRX_RULES`] object that contains
178177
// the compiled rules.
179178
//
@@ -199,6 +198,17 @@ enum YRX_RESULT yrx_rules_deserialize(const uint8_t *data,
199198
size_t len,
200199
struct YRX_RULES **rules);
201200

201+
// Iterates over the compiled rules, calling the callback function for each
202+
// rule.
203+
//
204+
// The `user_data` pointer can be used to provide additional context to your
205+
// callback function.
206+
//
207+
// See [`YRX_RULE_CALLBACK`] for more details.
208+
enum YRX_RESULT yrx_rules_iterate(struct YRX_RULES *rules,
209+
YRX_RULE_CALLBACK callback,
210+
void *user_data);
211+
202212
// Destroys a [`YRX_RULES`] object.
203213
void yrx_rules_destroy(struct YRX_RULES *rules);
204214

@@ -504,9 +514,9 @@ enum YRX_RESULT yrx_scanner_scan(struct YRX_SCANNER *scanner,
504514
// callback function. If the callback is not set, the scanner doesn't notify
505515
// about matching rules.
506516
//
507-
// See [`YRX_ON_MATCHING_RULE`] for more details.
517+
// See [`YRX_RULE_CALLBACK`] for more details.
508518
enum YRX_RESULT yrx_scanner_on_matching_rule(struct YRX_SCANNER *scanner,
509-
YRX_ON_MATCHING_RULE callback,
519+
YRX_RULE_CALLBACK callback,
510520
void *user_data);
511521

512522
// Specifies the output data structure for a module.

capi/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ includes:
9494
#![allow(clippy::not_unsafe_ptr_arg_deref)]
9595

9696
use std::cell::RefCell;
97-
use std::ffi::{c_char, CStr, CString};
97+
use std::ffi::{c_char, c_void, CStr, CString};
9898
use std::mem::ManuallyDrop;
9999
use std::ptr::slice_from_raw_parts_mut;
100100
use std::slice;
@@ -395,6 +395,43 @@ pub unsafe extern "C" fn yrx_rules_deserialize(
395395
}
396396
}
397397

398+
/// Callback function passed to [`yrx_scanner_on_matching_rule`] or
399+
/// [`yrx_rules_iterate`].
400+
///
401+
/// The callback receives a pointer to a rule, represented by a [`YRX_RULE`]
402+
/// structure. This pointer is guaranteed to be valid while the callback
403+
/// function is being executed, but it may be freed after the callback function
404+
/// returns, so you cannot use the pointer outside the callback.
405+
///
406+
/// It also receives the `user_data` pointer that can point to arbitrary data
407+
/// owned by the user.
408+
pub type YRX_RULE_CALLBACK =
409+
extern "C" fn(rule: *const YRX_RULE, user_data: *mut c_void) -> ();
410+
411+
/// Iterates over the compiled rules, calling the callback function for each
412+
/// rule.
413+
///
414+
/// The `user_data` pointer can be used to provide additional context to your
415+
/// callback function.
416+
///
417+
/// See [`YRX_RULE_CALLBACK`] for more details.
418+
#[no_mangle]
419+
pub unsafe extern "C" fn yrx_rules_iterate(
420+
rules: *mut YRX_RULES,
421+
callback: YRX_RULE_CALLBACK,
422+
user_data: *mut c_void,
423+
) -> YRX_RESULT {
424+
if let Some(rules) = rules.as_ref() {
425+
for r in rules.0.iter() {
426+
let rule = YRX_RULE(r);
427+
callback(&rule as *const YRX_RULE, user_data);
428+
}
429+
YRX_RESULT::SUCCESS
430+
} else {
431+
YRX_RESULT::INVALID_ARGUMENT
432+
}
433+
}
434+
398435
/// Destroys a [`YRX_RULES`] object.
399436
#[no_mangle]
400437
pub unsafe extern "C" fn yrx_rules_destroy(rules: *mut YRX_RULES) {

capi/src/scanner.rs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ use std::time::Duration;
44

55
use yara_x::errors::ScanError;
66

7-
use crate::{_yrx_set_last_error, YRX_RESULT, YRX_RULE, YRX_RULES};
7+
use crate::{
8+
_yrx_set_last_error, YRX_RESULT, YRX_RULE, YRX_RULES, YRX_RULE_CALLBACK,
9+
};
810

911
/// A scanner that scans data with a set of compiled YARA rules.
1012
pub struct YRX_SCANNER<'s> {
1113
inner: yara_x::Scanner<'s>,
12-
on_matching_rule: Option<(YRX_ON_MATCHING_RULE, *mut std::ffi::c_void)>,
14+
on_matching_rule: Option<(YRX_RULE_CALLBACK, *mut std::ffi::c_void)>,
1315
}
1416

1517
/// Creates a [`YRX_SCANNER`] object that can be used for scanning data with
@@ -113,34 +115,18 @@ pub unsafe extern "C" fn yrx_scanner_scan(
113115
YRX_RESULT::SUCCESS
114116
}
115117

116-
/// Callback function passed to the scanner via [`yrx_scanner_on_matching_rule`]
117-
/// which receives notifications about matching rules.
118-
///
119-
/// The callback receives a pointer to the matching rule, represented by a
120-
/// [`YRX_RULE`] structure. This pointer is guaranteed to be valid while the
121-
/// callback function is being executed, but it may be freed after the callback
122-
/// function returns, so you cannot use the pointer outside the callback.
123-
///
124-
/// It also receives the `user_data` pointer that was passed to the
125-
/// [`yrx_scanner_on_matching_rule`] function, which can point to arbitrary
126-
/// data owned by the user.
127-
pub type YRX_ON_MATCHING_RULE = extern "C" fn(
128-
rule: *const YRX_RULE,
129-
user_data: *mut std::ffi::c_void,
130-
) -> ();
131-
132118
/// Sets a callback function that is called by the scanner for each rule that
133119
/// matched during a scan.
134120
///
135121
/// The `user_data` pointer can be used to provide additional context to your
136122
/// callback function. If the callback is not set, the scanner doesn't notify
137123
/// about matching rules.
138124
///
139-
/// See [`YRX_ON_MATCHING_RULE`] for more details.
125+
/// See [`YRX_RULE_CALLBACK`] for more details.
140126
#[no_mangle]
141127
pub unsafe extern "C" fn yrx_scanner_on_matching_rule(
142128
scanner: *mut YRX_SCANNER,
143-
callback: YRX_ON_MATCHING_RULE,
129+
callback: YRX_RULE_CALLBACK,
144130
user_data: *mut std::ffi::c_void,
145131
) -> YRX_RESULT {
146132
if let Some(scanner) = scanner.as_mut() {

capi/src/tests.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@ use crate::{
99
yrx_buffer_destroy, yrx_last_error, yrx_metadata_destroy,
1010
yrx_patterns_destroy, yrx_rule_identifier, yrx_rule_metadata,
1111
yrx_rule_namespace, yrx_rule_patterns, yrx_rules_deserialize,
12-
yrx_rules_destroy, yrx_rules_serialize, yrx_scanner_create,
13-
yrx_scanner_destroy, yrx_scanner_on_matching_rule, yrx_scanner_scan,
14-
yrx_scanner_set_global_bool, yrx_scanner_set_global_float,
15-
yrx_scanner_set_global_int, yrx_scanner_set_global_str,
16-
yrx_scanner_set_timeout, YRX_BUFFER, YRX_RESULT, YRX_RULE,
12+
yrx_rules_destroy, yrx_rules_iter, yrx_rules_serialize,
13+
yrx_scanner_create, yrx_scanner_destroy, yrx_scanner_on_matching_rule,
14+
yrx_scanner_scan, yrx_scanner_set_global_bool,
15+
yrx_scanner_set_global_float, yrx_scanner_set_global_int,
16+
yrx_scanner_set_global_str, yrx_scanner_set_timeout, YRX_BUFFER,
17+
YRX_RESULT, YRX_RULE,
1718
};
1819

1920
use std::ffi::{c_void, CStr, CString};
2021

21-
extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) {
22+
extern "C" fn on_rule_iter(rule: *const YRX_RULE, user_data: *mut c_void) {
23+
let ptr = user_data as *mut i32;
24+
let count = unsafe { ptr.as_mut().unwrap() };
25+
*count += 1;
26+
}
27+
28+
extern "C" fn on_rule_match(rule: *const YRX_RULE, user_data: *mut c_void) {
2229
let mut ptr = std::ptr::null();
2330
let mut len = 0;
2431

@@ -95,6 +102,14 @@ fn capi() {
95102

96103
yrx_compiler_destroy(compiler);
97104

105+
let mut num_rules = 0;
106+
yrx_rules_iter(
107+
rules,
108+
on_rule_iter,
109+
&mut num_rules as *mut i32 as *mut c_void,
110+
);
111+
assert_eq!(num_rules, 1);
112+
98113
let mut buf: *mut YRX_BUFFER = std::ptr::null_mut();
99114

100115
yrx_rules_serialize(rules, &mut buf);
@@ -109,7 +124,7 @@ fn capi() {
109124
yrx_scanner_set_timeout(scanner, 60);
110125
yrx_scanner_on_matching_rule(
111126
scanner,
112-
callback,
127+
on_rule_match,
113128
&mut matches as *mut i32 as *mut c_void,
114129
);
115130

go/compiler_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,23 @@ func TestErrors(t *testing.T) {
168168
}, c.Errors())
169169
}
170170

171+
func TestRulesIter(t *testing.T) {
172+
c, err := NewCompiler()
173+
assert.NoError(t, err)
174+
175+
c.AddSource("rule test_1 { condition: true }")
176+
assert.NoError(t, err)
177+
178+
c.AddSource("rule test_2 { condition: true }")
179+
assert.NoError(t, err)
180+
181+
rules := c.Build().Slice()
182+
183+
assert.Len(t, rules, 2)
184+
assert.Equal(t, rules[0].Identifier(), "test_1")
185+
assert.Equal(t, rules[1].Identifier(), "test_2")
186+
}
187+
171188
func TestWarnings(t *testing.T) {
172189
c, err := NewCompiler()
173190
assert.NoError(t, err)

go/main.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,33 @@ package yara_x
55
// #cgo static_link pkg-config: --static yara_x_capi
66
// #include <yara_x.h>
77
//
8-
// uint64_t meta_i64(void* value) {
8+
// static uint64_t meta_i64(void* value) {
99
// return ((YRX_METADATA_VALUE*) value)->i64;
1010
// }
1111
//
12-
// double meta_f64(void* value) {
12+
// static double meta_f64(void* value) {
1313
// return ((YRX_METADATA_VALUE*) value)->f64;
1414
// }
1515
//
16-
// bool meta_bool(void* value) {
16+
// static bool meta_bool(void* value) {
1717
// return ((YRX_METADATA_VALUE*) value)->boolean;
1818
// }
1919
//
20-
// char* meta_str(void* value) {
20+
// static char* meta_str(void* value) {
2121
// return ((YRX_METADATA_VALUE*) value)->string;
2222
// }
2323
//
24-
// YRX_METADATA_BYTES* meta_bytes(void* value) {
24+
// static YRX_METADATA_BYTES* meta_bytes(void* value) {
2525
// return &(((YRX_METADATA_VALUE*) value)->bytes);
2626
// }
27+
//
28+
// void onRule(YRX_RULE*, void*);
2729
import "C"
30+
2831
import (
2932
"errors"
3033
"runtime"
34+
"runtime/cgo"
3135
"unsafe"
3236
)
3337

@@ -99,6 +103,34 @@ func (r *Rules) Destroy() {
99103
runtime.SetFinalizer(r, nil)
100104
}
101105

106+
// This is the callback called by yrx_rules_iter, when Rules.GetRules is
107+
// called.
108+
//export onRule
109+
func onRule(rule *C.YRX_RULE, handle unsafe.Pointer) {
110+
h := (cgo.Handle)(handle)
111+
rules, ok := h.Value().(*[]*Rule)
112+
if !ok {
113+
panic("onRule didn't receive a *[]Rule")
114+
}
115+
*rules = append(*rules, newRule(rule))
116+
}
117+
118+
// Slice returns a slice with all the individual rules contained in this
119+
// set of compiled rules.
120+
func (r *Rules) Slice() []*Rule {
121+
rules := make([]*Rule, 0)
122+
handle := cgo.NewHandle(&rules)
123+
defer handle.Delete()
124+
125+
C.yrx_rules_iterate(
126+
r.cRules,
127+
C.YRX_RULE_CALLBACK(C.onRule),
128+
unsafe.Pointer(handle))
129+
130+
runtime.KeepAlive(r)
131+
return rules
132+
}
133+
102134
// Rule represents a YARA rule.
103135
type Rule struct {
104136
namespace string

0 commit comments

Comments
 (0)