Skip to content

Commit

Permalink
feat(capi): implement APIs for obtaining rule's metadata.
Browse files Browse the repository at this point in the history
Remove `yrx_rule_metadata_as_json`.
  • Loading branch information
plusvic committed May 23, 2024
1 parent 9e489bd commit 9f90eaa
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 56 deletions.
59 changes: 46 additions & 13 deletions capi/include/yara_x.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
// constructs that YARA-X doesn't accept by default.
#define YRX_RELAXED_RE_SYNTAX 2

// Metadata value types.
typedef enum YRX_METADATA_VALUE_TYPE {
INTEGER,
FLOAT,
BOOLEAN,
STRING,
BYTES,
} YRX_METADATA_VALUE_TYPE;

// Error codes returned by functions in this API.
typedef enum YRX_RESULT {
// Everything was OK.
Expand Down Expand Up @@ -79,6 +88,41 @@ typedef struct YRX_BUFFER {
size_t length;
} YRX_BUFFER;

// Represents a metadata value that contains raw bytes.
typedef struct YRX_METADATA_BYTES {
// Number of bytes.
size_t length;
// Pointer to the bytes.
uint8_t *data;
} YRX_METADATA_BYTES;

// Metadata value.
typedef union YRX_METADATA_VALUE {
int64_t integer;
double float;
bool boolean;
char *string;
struct YRX_METADATA_BYTES bytes;
} YRX_METADATA_VALUE;

// A metadata entry.
typedef struct YRX_METADATA_ENTRY {
// Metadata identifier.
char *identifier;
enum YRX_METADATA_VALUE_TYPE value_type;
union YRX_METADATA_VALUE value;
} YRX_METADATA_ENTRY;

// Represents the metadata associated to a rule.
typedef struct YRX_METADATA {
// Number of metadata entries.
size_t num_entries;
// Pointer to an array of YRX_METADATA_ENTRY structures. The array has
// num_entries items. If num_entries is zero this pointer is invalid
// and should not be de-referenced.
struct YRX_METADATA_ENTRY *entries;
} YRX_METADATA;

// Contains information about a pattern match.
typedef struct YRX_MATCH {
// Offset within the data where the match occurred.
Expand Down Expand Up @@ -175,19 +219,8 @@ enum YRX_RESULT yrx_rule_namespace(const struct YRX_RULE *rule,
const uint8_t **ns,
size_t *len);

// Returns the rule metadata encoded as JSON.
//
// In the address indicated by the `buf` pointer, the function will copy a
// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer
// that contains the metadata encoded as JSON. This structure has a pointer
// to the data itself, and its length.
//
// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`].
//
// If the rule doesn't have any metadata, this function returns
// [`YRX_RESULT::NO_METADATA`].
enum YRX_RESULT yrx_rule_metadata_as_json(const struct YRX_RULE *rule,
struct YRX_BUFFER **buf);
// Destroys a [`YRX_METADATA`] object.
void yrx_metadata_destroy(struct YRX_METADATA *metadata);

// Returns all the patterns defined by a rule.
//
Expand Down
189 changes: 162 additions & 27 deletions capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,88 @@ pub struct YRX_RULES(yara_x::Rules);
/// A single YARA rule.
pub struct YRX_RULE<'a, 'r>(yara_x::Rule<'a, 'r>);

/// Represents the metadata associated to a rule.
#[repr(C)]
pub struct YRX_METADATA {
/// Number of metadata entries.
num_entries: usize,
/// Pointer to an array of YRX_METADATA_ENTRY structures. The array has
/// num_entries items. If num_entries is zero this pointer is invalid
/// and should not be de-referenced.
entries: *mut YRX_METADATA_ENTRY,
}

impl Drop for YRX_METADATA {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(slice_from_raw_parts_mut(
self.entries,
self.num_entries,
)));
}
}
}

/// Metadata value types.
#[repr(C)]
#[allow(missing_docs)]
pub enum YRX_METADATA_VALUE_TYPE {
INTEGER,
FLOAT,
BOOLEAN,
STRING,
BYTES,
}

/// Represents a metadata value that contains raw bytes.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct YRX_METADATA_BYTES {
/// Number of bytes.
length: usize,
/// Pointer to the bytes.
data: *mut u8,
}

/// Metadata value.
#[repr(C)]
union YRX_METADATA_VALUE {
integer: i64,
float: f64,
boolean: bool,
string: *mut c_char,
bytes: YRX_METADATA_BYTES,
}

/// A metadata entry.
#[repr(C)]
pub struct YRX_METADATA_ENTRY {
/// Metadata identifier.
identifier: *mut c_char,
value_type: YRX_METADATA_VALUE_TYPE,
value: YRX_METADATA_VALUE,
}

impl Drop for YRX_METADATA_ENTRY {
fn drop(&mut self) {
unsafe {
drop(CString::from_raw(self.identifier));
match self.value_type {
YRX_METADATA_VALUE_TYPE::STRING => {
drop(CString::from_raw(self.value.string));
}
YRX_METADATA_VALUE_TYPE::BYTES => {
drop(Box::from_raw(slice_from_raw_parts_mut(
self.value.bytes.data,
self.value.bytes.length,
)));
}
_ => {}
}
}
}
}

/// A set of patterns declared in a YARA rule.
#[repr(C)]
pub struct YRX_PATTERNS {
Expand Down Expand Up @@ -354,38 +436,91 @@ pub unsafe extern "C" fn yrx_rule_namespace(
}
}

/// Returns the rule metadata encoded as JSON.
///
/// In the address indicated by the `buf` pointer, the function will copy a
/// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer
/// that contains the metadata encoded as JSON. This structure has a pointer
/// to the data itself, and its length.
/// Returns the metadata associated to a rule.
///
/// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`].
/// The metadata is represented by a [`YRX_METADATA`] object that must be
/// destroyed with [`yrx_metadata_destroy`] when not needed anymore.
///
/// If the rule doesn't have any metadata, this function returns
/// [`YRX_RESULT::NO_METADATA`].
#[no_mangle]
pub unsafe extern "C" fn yrx_rule_metadata_as_json(
/// This function returns a null pointer when `rule` is null or the
/// rule doesn't have any metadata.
pub unsafe extern "C" fn yrx_rule_metadata(
rule: *const YRX_RULE,
buf: &mut *mut YRX_BUFFER,
) -> YRX_RESULT {
if let Some(rule) = rule.as_ref() {
let metadata = rule.0.metadata();
if metadata.is_empty() {
return YRX_RESULT::NO_METADATA;
}
let json = metadata.into_json().to_string();
let mut json = ManuallyDrop::new(json);
*buf = Box::into_raw(Box::new(YRX_BUFFER {
data: json.as_mut_ptr(),
length: json.len(),
}));
LAST_ERROR.set(None);
YRX_RESULT::SUCCESS
) -> *mut YRX_METADATA {
let metadata = if let Some(rule) = rule.as_ref() {
rule.0.metadata()
} else {
YRX_RESULT::INVALID_ARGUMENT
return std::ptr::null_mut();
};

if metadata.is_empty() {
return std::ptr::null_mut();
}

let mut entries = Vec::with_capacity(metadata.len());

for (identifier, value) in metadata {
let identifier = CString::new(identifier).unwrap().into_raw();

match value {
yara_x::MetaValue::Integer(v) => {
entries.push(YRX_METADATA_ENTRY {
identifier,
value_type: YRX_METADATA_VALUE_TYPE::INTEGER,
value: YRX_METADATA_VALUE { integer: v },
});
}
yara_x::MetaValue::Float(v) => {
entries.push(YRX_METADATA_ENTRY {
identifier,
value_type: YRX_METADATA_VALUE_TYPE::FLOAT,
value: YRX_METADATA_VALUE { float: v },
});
}
yara_x::MetaValue::Bool(v) => {
entries.push(YRX_METADATA_ENTRY {
identifier,
value_type: YRX_METADATA_VALUE_TYPE::BOOLEAN,
value: YRX_METADATA_VALUE { boolean: v },
});
}
yara_x::MetaValue::String(v) => {
entries.push(YRX_METADATA_ENTRY {
identifier,
value_type: YRX_METADATA_VALUE_TYPE::STRING,
value: YRX_METADATA_VALUE {
string: CString::new(v).unwrap().into_raw(),
},
});
}
yara_x::MetaValue::Bytes(v) => {
let v = v.to_vec().into_boxed_slice();
let mut v = ManuallyDrop::new(v);
entries.push(YRX_METADATA_ENTRY {
identifier,
value_type: YRX_METADATA_VALUE_TYPE::BYTES,
value: YRX_METADATA_VALUE {
bytes: YRX_METADATA_BYTES {
data: v.as_mut_ptr(),
length: v.len(),
},
},
});
}
};
}

let mut entries = ManuallyDrop::new(entries);

Box::into_raw(Box::new(YRX_METADATA {
num_entries: entries.len(),
entries: entries.as_mut_ptr(),
}))
}

/// Destroys a [`YRX_METADATA`] object.
#[no_mangle]
pub unsafe extern "C" fn yrx_metadata_destroy(metadata: *mut YRX_METADATA) {
drop(Box::from_raw(metadata));
}

/// Returns all the patterns defined by a rule.
Expand Down
28 changes: 12 additions & 16 deletions capi/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ use crate::compiler::{
yrx_compiler_destroy, yrx_compiler_new_namespace,
};
use crate::{
yrx_buffer_destroy, yrx_last_error, yrx_patterns_destroy,
yrx_rule_identifier, yrx_rule_metadata_as_json, yrx_rule_namespace,
yrx_rule_patterns, yrx_rules_deserialize, yrx_rules_destroy,
yrx_rules_serialize, yrx_scanner_create, yrx_scanner_destroy,
yrx_scanner_on_matching_rule, yrx_scanner_scan,
yrx_buffer_destroy, yrx_last_error, yrx_metadata_destroy,
yrx_patterns_destroy, yrx_rule_identifier, yrx_rule_metadata,
yrx_rule_namespace, yrx_rule_patterns, yrx_rules_deserialize,
yrx_rules_destroy, 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_RULE,
};
use std::ffi::{c_void, CString};
use std::slice;

extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) {
let mut ptr = std::ptr::null();
Expand All @@ -25,18 +24,13 @@ extern "C" fn callback(rule: *const YRX_RULE, user_data: *mut c_void) {
yrx_rule_namespace(rule, &mut ptr, &mut len);
yrx_rule_identifier(rule, &mut ptr, &mut len);

let mut metadata: *mut YRX_BUFFER = std::ptr::null_mut();
yrx_rule_metadata_as_json(rule, &mut metadata);

let json = slice::from_raw_parts((*metadata).data, (*metadata).length);
let json_str = String::from_utf8(json.to_vec()).unwrap();

assert_eq!(json_str.as_str(), r#"[["some_int",1]]"#);

yrx_buffer_destroy(metadata);

let metadata = yrx_rule_metadata(rule);
let patterns = yrx_rule_patterns(rule);

assert_eq!((*patterns).num_patterns, 1);
assert_eq!((*metadata).num_entries, 3);

yrx_metadata_destroy(metadata);
yrx_patterns_destroy(patterns);
}

Expand All @@ -55,6 +49,8 @@ fn capi() {
b"rule test {\
meta: \
some_int = 1 \
some_string = \"foo\" \
some_bytes = \"\\x01\\x00\\x02\" \
strings: \
$foo = \"foo\" \
condition: \
Expand Down

0 comments on commit 9f90eaa

Please sign in to comment.