Skip to content

Commit

Permalink
feat: introduce the [(yara.enum_value).i64 = X] option that allow e…
Browse files Browse the repository at this point in the history
…num values in the range `i64::MIN` to `i64::MAX`

With this change you can declare enums like this:

```protobuf
  enum MyEnum {
    Foo = 0 [(yara.enum_value).i64 = 0x7fffffffffff];
    Bar = 1 [(yara.enum_value).i64 = -1];
  }
```
  • Loading branch information
plusvic committed Sep 20, 2023
1 parent fb3491f commit 7539bb8
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 28 deletions.
18 changes: 15 additions & 3 deletions docs/Module Developer's Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -805,11 +805,23 @@ enum Threshold {
}
```

----
However, because tag numbers are of type `i32`, the range of possible values
goes from `i32::MIN` to `i32::MAX`. For larger values you need to use an
alternative approach:

```protobuf
enum MachO {
MAGIC = 0 [(yara.enum_value).i64 = 0xfeedface];
CIGAM = 1 [(yara.enum_value).i64 = 0xcefaedfe];
}
```

**NOTE**: The maximum possible value for an enum item is `0x7fffffff`
In the enum above the values of `MAGIC` and `CIGAM` are not `0` and `1` but
`0xfeedface` and `0xcefaedfe` respectively. Tag numbers are still present
because they are required in protobuf, however, their values are irrelevant.
The `(yara.enum_value).i64` option has priority when assigning a value to each
enum item, and it allows setting values from `i64::MIN` to `i64::MAX`.

----

### Inline enums

Expand Down
8 changes: 8 additions & 0 deletions yara-x-proto/src/yara.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ message EnumOptions {
optional bool inline = 2;
}

message EnumValueOptions {
optional int64 i64 = 1;
}

extend google.protobuf.FileOptions {
optional ModuleOptions module_options = 51503;
}
Expand All @@ -41,4 +45,8 @@ extend google.protobuf.MessageOptions {

extend google.protobuf.EnumOptions {
optional EnumOptions enum_options = 51506;
}

extend google.protobuf.EnumValueOptions {
optional EnumValueOptions enum_value = 51507;
}
2 changes: 2 additions & 0 deletions yara-x/src/modules/protos/test_proto2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ message TestProto2 {
enum Enumeration {
ITEM_0 = 0;
ITEM_1 = 1;
ITEM_2 = 2 [(yara.enum_value).i64 = 0x7fffffffffff];
ITEM_3 = 3 [(yara.enum_value).i64 = -1];
}

optional Enumeration enumeration = 100;
Expand Down
2 changes: 2 additions & 0 deletions yara-x/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2919,6 +2919,8 @@ fn test_proto2_module() {

condition_true!(r#"test_proto2.Enumeration.ITEM_0 == 0"#);
condition_true!(r#"test_proto2.Enumeration.ITEM_1 == 1"#);
condition_true!(r#"test_proto2.Enumeration.ITEM_2 == 0x7fffffffffff"#);
condition_true!(r#"test_proto2.Enumeration.ITEM_3 == -1"#);

condition_true!(r#"test_proto2.INLINE_0x1000 == 0x1000"#);

Expand Down
92 changes: 67 additions & 25 deletions yara-x/src/types/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ use std::rc::Rc;

use bstr::BString;
use indexmap::IndexMap;
use protobuf::reflect::Syntax;
use protobuf::reflect::{
EnumDescriptor, FieldDescriptor, MessageDescriptor, ReflectMapRef,
ReflectRepeatedRef, ReflectValueRef, RuntimeFieldType, RuntimeType,
};
use protobuf::reflect::{EnumValueDescriptor, Syntax};
use protobuf::MessageDyn;
use serde::{Deserialize, Serialize};

use yara_x_proto::exts::enum_options as yara_enum_options;
use yara_x_proto::exts::field_options as yara_field_options;
use yara_x_proto::exts::module_options as yara_module_options;
use yara_x_proto::exts::enum_options;
use yara_x_proto::exts::enum_value;
use yara_x_proto::exts::field_options;
use yara_x_proto::exts::module_options;

use crate::types::{Array, Map, TypeValue, Value};

Expand Down Expand Up @@ -313,7 +314,7 @@ impl Struct {
for item in enum_.values() {
fields.push(StructField {
type_value: TypeValue::Integer(Value::Const(
item.value() as i64,
Self::enum_value(&item),
)),
number: 0,
name: item.name().to_owned(),
Expand All @@ -328,7 +329,7 @@ impl Struct {
if let Some(existing_field) = enum_struct.add_field(
item.name(),
TypeValue::Integer(Value::Const(
item.value() as i64
Self::enum_value(&item),
)),
) {
panic!(
Expand Down Expand Up @@ -367,10 +368,10 @@ impl Struct {
/// Returns true if the given message is the YARA module's root message.
fn is_root_msg(msg_descriptor: &MessageDescriptor) -> bool {
let file_descriptor = msg_descriptor.file_descriptor();
if let Some(module_options) =
yara_module_options.get(&file_descriptor.proto().options)
if let Some(options) =
module_options.get(&file_descriptor.proto().options)
{
module_options.root_message.unwrap() == msg_descriptor.name()
options.root_message.unwrap() == msg_descriptor.name()
} else {
false
}
Expand All @@ -393,12 +394,10 @@ impl Struct {
///
/// Here the enum will be named `my_enum` instead of `Enumeration`.
fn enum_name(enum_descriptor: &EnumDescriptor) -> String {
if let Some(enum_options) =
yara_enum_options.get(&enum_descriptor.proto().options)
if let Some(options) =
enum_options.get(&enum_descriptor.proto().options)
{
enum_options
.name
.unwrap_or_else(|| enum_descriptor.name().to_owned())
options.name.unwrap_or_else(|| enum_descriptor.name().to_owned())
} else {
enum_descriptor.name().to_owned()
}
Expand Down Expand Up @@ -436,15 +435,60 @@ impl Struct {
/// in the enum are added directly as fields of the module, or the struct
/// that contains the enum.
fn enum_is_inline(enum_descriptor: &EnumDescriptor) -> bool {
if let Some(enum_options) =
yara_enum_options.get(&enum_descriptor.proto().options)
if let Some(options) =
enum_options.get(&enum_descriptor.proto().options)
{
enum_options.inline.unwrap_or(false)
options.inline.unwrap_or(false)
} else {
false
}
}

/// Given a [`EnumValueDescriptor`] returns the value associated to that
/// enum item.
///
/// The value for each item in an enum can be specified in two ways: by
/// means of the tag number, or by using a special option. Let's see an
/// example of the first case:
///
/// ```text
/// enum MyEnum {
/// ITEM_0 = 0;
/// ITEM_1 = 1;
/// }
/// ```
///
/// In this enum the value of `ITEM_0` is 0, and the value of `ITEM_1` is
/// 1. The tag number associated to each item determines its value. However
/// this approach has one limitation, tag number are of type `i32` and
/// therefore they are limited to the range `-2147483648,2147483647`. For
/// larger values you need to use the second approach:
///
/// ```text
/// enum MyEnum {
/// ITEM_0 = 0 [(yara.enum_value).i64 = 0x7fffffffffff];
/// ITEM_1 = 1 [(yara.enum_value).i64 = -1];;
/// }
/// ```
///
/// In this other case tag number are maintained because they are required
/// in every protobuf enum, however, the value associated to each items is
/// not determined by the field number, but by the `(yara.enum_value).i64`
/// option.
///
/// What this function returns is the value associated to an enum item,
/// returning the value set via the `(yara.enum_value).i64` option, if any,
/// or the tag number.
fn enum_value(enum_value_descriptor: &EnumValueDescriptor) -> i64 {
if let Some(options) =
enum_value.get(&enum_value_descriptor.proto().options)
{
options.i64.unwrap_or_else(|| enum_value_descriptor.value() as i64)
} else {
enum_value_descriptor.value() as i64
}
}

/// Given a [`FieldDescriptor`] returns the name that this field will
/// have in the corresponding [`Struct`].
///
Expand All @@ -459,12 +503,10 @@ impl Struct {
/// Here the `foo` field will be named `bar` when the protobuf is converted
/// into a [`Struct`].
fn field_name(field_descriptor: &FieldDescriptor) -> String {
if let Some(field_options) =
yara_field_options.get(&field_descriptor.proto().options)
if let Some(options) =
field_options.get(&field_descriptor.proto().options)
{
field_options
.name
.unwrap_or_else(|| field_descriptor.name().to_owned())
options.name.unwrap_or_else(|| field_descriptor.name().to_owned())
} else {
field_descriptor.name().to_owned()
}
Expand All @@ -481,10 +523,10 @@ impl Struct {
/// ```
///
fn ignore_field(field_descriptor: &FieldDescriptor) -> bool {
if let Some(field_options) =
yara_field_options.get(&field_descriptor.proto().options)
if let Some(options) =
field_options.get(&field_descriptor.proto().options)
{
field_options.ignore.unwrap_or(false)
options.ignore.unwrap_or(false)
} else {
false
}
Expand Down

0 comments on commit 7539bb8

Please sign in to comment.