Skip to content

Commit

Permalink
feat: allow controlling the access to certain fields in a module usin…
Browse files Browse the repository at this point in the history
…g compiler features.

This introduces a `Compiler::enable_feature` method that allows enabling features with arbitrary names. These features can be used in the `.proto` file that defines a YARA-X module for controlling whether a structure field is accesible or not. You can force a field to become accessible only when one more features has been enabled in the compiler.

For the time being this only works when `.proto` files are compiled using the official Protobuf compiler (i.e: the `cargo --features=protoc`)
  • Loading branch information
plusvic committed Sep 4, 2024
1 parent 5b1c348 commit 230ad4c
Showing 17 changed files with 264 additions and 37 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -75,6 +75,11 @@ num-derive = "0.4.2"
p256 = "0.13.2"
p384 = "0.13.0"
pretty_assertions = "1.4.0"
#protobuf = { path = "../rust-protobuf/protobuf" }
#protobuf-codegen = { path = "../rust-protobuf/protobuf-codegen" }
#protobuf-json-mapping = { path = "../rust-protobuf/protobuf-json-mapping" }
#protobuf-parse = { path = "../rust-protobuf/protobuf-parse" }
#protobuf-support = { path = "../rust-protobuf/protobuf-support" }
protobuf = "3.5.0"
protobuf-codegen = "3.5.0"
protobuf-json-mapping = "3.5.0"
4 changes: 4 additions & 0 deletions lib/src/compiler/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use itertools::Itertools;
use rustc_hash::FxHashSet;
use std::mem::size_of;
use std::rc::Rc;

@@ -34,6 +35,9 @@ pub(in crate::compiler) struct CompileContext<'a, 'src, 'sym> {
/// Warnings generated during the compilation.
pub warnings: &'a mut Warnings,

/// Enabled features. See [`crate::Compiler::enable_feature`] for details.
pub features: &'a FxHashSet<String>,

/// Stack of variables. These are local variables used during the
/// evaluation of rule conditions, for example for storing loop variables.
pub vars: VarStack,
18 changes: 9 additions & 9 deletions lib/src/compiler/emit.rs
Original file line number Diff line number Diff line change
@@ -331,36 +331,36 @@ fn emit_expr(
SymbolKind::Func(func) => {
emit_func_call(ctx, instr, func);
}
SymbolKind::Field(index, root) => {
SymbolKind::Field { index, is_root, .. } => {
let index: i32 = (*index).try_into().unwrap();
match symbol.type_value() {
TypeValue::Integer(_) => {
ctx.lookup_list.push((index, *root));
ctx.lookup_list.push((index, *is_root));
emit_lookup_integer(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Float(_) => {
ctx.lookup_list.push((index, *root));
ctx.lookup_list.push((index, *is_root));
emit_lookup_float(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Bool(_) => {
ctx.lookup_list.push((index, *root));
ctx.lookup_list.push((index, *is_root));
emit_lookup_bool(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::String(_) => {
ctx.lookup_list.push((index, *root));
ctx.lookup_list.push((index, *is_root));
emit_lookup_string(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Struct(_) => {
ctx.lookup_list.push((index, *root));
ctx.lookup_list.push((index, *is_root));
emit_lookup_object(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
TypeValue::Array(_) | TypeValue::Map(_) => {
ctx.lookup_list.push((index, *root));
ctx.lookup_list.push((index, *is_root));
emit_lookup_object(ctx, instr);
assert!(ctx.lookup_list.is_empty());
}
@@ -909,8 +909,8 @@ fn emit_field_access(
// Rust code.
for operand in operands.iter_mut().dropping_back(1) {
if let Expr::Ident { symbol } = operand {
if let SymbolKind::Field(index, root) = symbol.kind() {
ctx.lookup_list.push((*index as i32, *root));
if let SymbolKind::Field { index, is_root, .. } = symbol.kind() {
ctx.lookup_list.push((*index as i32, *is_root));
continue;
}
}
13 changes: 13 additions & 0 deletions lib/src/compiler/errors.rs
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ pub struct EmitWasmError(#[from] anyhow::Error);
pub enum CompileError {
AssignmentMismatch(Box<AssignmentMismatch>),
ConflictingRuleIdentifier(Box<ConflictingRuleIdentifier>),
CustomError(Box<CustomError>),
DuplicateModifier(Box<DuplicateModifier>),
DuplicatePattern(Box<DuplicatePattern>),
DuplicateRule(Box<DuplicateRule>),
@@ -587,3 +588,15 @@ pub struct InvalidModifier {
error: String,
error_loc: CodeLoc,
}

/// A custom error has occurred.
#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)]
#[associated_enum(CompileError)]
#[error(code = "E034", title = "{title}")]
#[label("{error}", error_loc)]
pub struct CustomError {
report: Report,
title: String,
error: String,
error_loc: CodeLoc,
}
18 changes: 18 additions & 0 deletions lib/src/compiler/ir/ast2ir.rs
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ use crate::compiler::ir::{
};
use crate::compiler::report::ReportBuilder;
use crate::compiler::{warnings, CompileContext, CompileError};
use crate::errors::CustomError;
use crate::modules::BUILTIN_MODULES;
use crate::re;
use crate::re::parser::Error;
@@ -599,6 +600,23 @@ pub(in crate::compiler) fn expr_from_ast(
}

let symbol = symbol.unwrap();

// If the symbol is a structure field, and it has an ACL, check if
// the conditions imposed in the ACL are met. If the conditions are
// not met an error is raised.
if let SymbolKind::Field { acl: Some(acl), .. } = symbol.kind() {
for entry in acl {
if !ctx.features.contains(&entry.allow_if) {
return Err(CustomError::build(
ctx.report_builder,
entry.error_title.clone(),
entry.error_label.clone(),
ident.span().into(),
));
}
}
}

#[cfg(feature = "constant-folding")]
{
let type_value = symbol.type_value();
5 changes: 3 additions & 2 deletions lib/src/compiler/ir/hex2hir.rs
Original file line number Diff line number Diff line change
@@ -175,12 +175,12 @@ fn hex_byte_to_class(b: &ast::HexByte) -> hir::ClassBytes {
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

use regex_syntax::hir::{
Class, ClassBytes, ClassBytesRange, Dot, Hir, HirKind, Repetition,
};
use yara_x_parser::ast;
use rustc_hash::FxHashSet;

use yara_x_parser::ast;
use yara_x_parser::ast::{
HexAlternative, HexJump, HexPattern, HexToken, HexTokens, Ident,
};
@@ -218,6 +218,7 @@ mod tests {
let mut ctx = CompileContext {
relaxed_re_syntax: false,
current_symbol_table: None,
features: &FxHashSet::default(),
symbol_table: &mut symbol_table,
report_builder: &mut report_builder,
current_rule_patterns: &mut rule_patterns,
4 changes: 2 additions & 2 deletions lib/src/compiler/ir/tests/testdata/1.folding.ir
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@ RULE test_4
AND
EQ
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Field(0, true) }
IDENT Symbol { type_value: integer(unknown), kind: Field(1, false) }
IDENT Symbol { type_value: struct, kind: Field { index: 0, is_root: true, acl: None } }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 1, is_root: false, acl: None } }
CONST integer(0)
NOT
CONST boolean(false)
16 changes: 8 additions & 8 deletions lib/src/compiler/ir/tests/testdata/2.folding.ir
Original file line number Diff line number Diff line change
@@ -7,13 +7,13 @@ RULE test
PATTERN_COUNT PatternIdx(1)
FOR_IN
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Field(0, true) }
IDENT Symbol { type_value: array, kind: Field(19, false) }
IDENT Symbol { type_value: struct, kind: Field { index: 0, is_root: true, acl: None } }
IDENT Symbol { type_value: array, kind: Field { index: 19, is_root: false, acl: None } }
AND
LE
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Var(Var { ty: struct, index: 12 }) }
IDENT Symbol { type_value: integer(unknown), kind: Field(0, false) }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 0, is_root: false, acl: None } }
PATTERN_OFFSET PatternIdx(0) INDEX
IDENT Symbol { type_value: integer(unknown), kind: Var(Var { ty: integer, index: 0 }) }
LE
@@ -22,14 +22,14 @@ RULE test
ADD
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Var(Var { ty: struct, index: 12 }) }
IDENT Symbol { type_value: integer(unknown), kind: Field(0, false) }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 0, is_root: false, acl: None } }
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Var(Var { ty: struct, index: 12 }) }
IDENT Symbol { type_value: integer(unknown), kind: Field(1, false) }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 1, is_root: false, acl: None } }
LE
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Var(Var { ty: struct, index: 12 }) }
IDENT Symbol { type_value: integer(unknown), kind: Field(0, false) }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 0, is_root: false, acl: None } }
PATTERN_OFFSET PatternIdx(1) INDEX
IDENT Symbol { type_value: integer(unknown), kind: Var(Var { ty: integer, index: 6 }) }
LE
@@ -38,8 +38,8 @@ RULE test
ADD
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Var(Var { ty: struct, index: 12 }) }
IDENT Symbol { type_value: integer(unknown), kind: Field(0, false) }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 0, is_root: false, acl: None } }
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Var(Var { ty: struct, index: 12 }) }
IDENT Symbol { type_value: integer(unknown), kind: Field(1, false) }
IDENT Symbol { type_value: integer(unknown), kind: Field { index: 1, is_root: false, acl: None } }

8 changes: 4 additions & 4 deletions lib/src/compiler/ir/tests/testdata/3.folding.ir
Original file line number Diff line number Diff line change
@@ -3,16 +3,16 @@ RULE test
EQ
FN_CALL
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Field(0, true) }
IDENT Symbol { type_value: function, kind: Field(4, false) }
IDENT Symbol { type_value: struct, kind: Field { index: 0, is_root: true, acl: None } }
IDENT Symbol { type_value: function, kind: Field { index: 4, is_root: false, acl: None } }
CONST integer(0)
FILESIZE
CONST string("feba6c919e3797e7778e8f2e85fa033d")
EQ
FN_CALL
FIELD_ACCESS
IDENT Symbol { type_value: struct, kind: Field(0, true) }
IDENT Symbol { type_value: function, kind: Field(4, false) }
IDENT Symbol { type_value: struct, kind: Field { index: 0, is_root: true, acl: None } }
IDENT Symbol { type_value: function, kind: Field { index: 4, is_root: false, acl: None } }
CONST integer(0)
FILESIZE
CONST string("275876e34cf609db118f3d84b799a790")
62 changes: 62 additions & 0 deletions lib/src/compiler/mod.rs
Original file line number Diff line number Diff line change
@@ -361,6 +361,10 @@ pub struct Compiler<'a> {
/// Errors generated while compiling the rules.
errors: Vec<CompileError>,

/// Features enabled for this compiler. See [`Compiler::enable_feature`]
/// for details.
features: FxHashSet<String>,

/// Optional writer where the compiler writes the IR produced by each rule.
/// This is used for test cases and debugging.
#[cfg(test)]
@@ -428,6 +432,7 @@ impl<'a> Compiler<'a> {
next_pattern_id: PatternId(0),
current_pattern_id: PatternId(0),
current_namespace: default_namespace,
features: FxHashSet::default(),
warnings: Warnings::default(),
errors: Vec::new(),
rules: Vec::new(),
@@ -719,6 +724,62 @@ impl<'a> Compiler<'a> {
rules
}

/// Enables a feature on this compiler.
///
/// When defining the structure of a module in a `.proto` file, you can
/// specify that certain fields are accessible only when one or more
/// features are enabled. For example, the snippet below shows the
/// definition of a field named `requires_foo_and_bar`, which can be
/// accessed only when both features "foo" and "bar" are enabled.
///
/// ```protobuf
/// optional uint64 requires_foo_and_bar = 500 [
/// (yara.field_options) = {
/// acl: [
/// {
/// allow_if: "foo",
/// error_title: "foo is required",
/// error_label: "this field was used without foo"
/// },
/// {
/// allow_if: "bar",
/// error_title: "bar is required",
/// error_label: "this field was used without bar"
/// }
/// ]
/// }
/// ];
/// ```
///
/// If some of the required features are not enabled, using this field in
/// a YARA rule will cause an error while compiling the rules. The error
/// looks like:
///
/// ```text
/// error[E034]: foo is required
/// --> line:5:29
/// |
/// 5 | test_proto2.requires_foo_and_bar == 0
/// | ^^^^^^^^^^^^^^^^^^^^ this field was used without foo
/// |
/// ```
///
/// Notice that both the title and label in the error message are defined
/// in the .proto file.
///
/// # Important
///
/// This API is hidden from the public documentation because it is unstable
/// and subject to change.
#[doc(hidden)]
pub fn enable_feature<F: Into<String>>(
&mut self,
feature: F,
) -> &mut Self {
self.features.insert(feature.into());
self
}

/// Tell the compiler that a YARA module is not supported.
///
/// Import statements for ignored modules will be ignored without
@@ -1081,6 +1142,7 @@ impl<'a> Compiler<'a> {
warnings: &mut self.warnings,
vars: VarStack::new(),
for_of_depth: 0,
features: &self.features,
};

// Convert the patterns from AST to IR. This populates the
10 changes: 8 additions & 2 deletions lib/src/compiler/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -642,7 +642,7 @@ fn continue_after_error() {
}

#[test]
fn errors_2() {
fn conflicting_identifiers_error() {
assert_eq!(
Compiler::new()
.define_global("foo", 1)
@@ -657,7 +657,10 @@ fn errors_2() {
| ^^^ identifier already in use by a module or global variable
|"
);
}

#[test]
fn duplicate_rule_error() {
assert_eq!(
Compiler::new()
.add_source("rule foo : first {condition: true}")
@@ -677,8 +680,11 @@ fn errors_2() {
| --- note: `foo` declared here for the first time
|"
);
}

#[cfg(feature = "constant-folding")]
#[cfg(feature = "constant-folding")]
#[test]
fn number_out_of_range_error() {
assert_eq!(
Compiler::new()
.add_source(
19 changes: 19 additions & 0 deletions lib/src/modules/protos/test_proto2.proto
Original file line number Diff line number Diff line change
@@ -186,6 +186,25 @@ message TestProto2 {
}

optional uint64 file_size = 400;

/// This field is accessible only if the feature "foo" is enabled while
// compiling the YARA rules.
optional uint64 requires_foo_and_bar = 500 [
(yara.field_options) = {
acl: [
{
allow_if: "foo",
error_title: "foo is required",
error_label: "this field was used without foo"
},
{
allow_if: "bar",
error_title: "bar is required",
error_label: "this field was used without bar"
}
]
}
];
}

enum TopLevelEnumeration {
Loading

0 comments on commit 230ad4c

Please sign in to comment.