Skip to content

Commit 3d535af

Browse files
authored
feat: implement ACLs for module fields (#243)
Allow controlling the access to certain fields in a module using 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.
1 parent 20007bb commit 3d535af

35 files changed

+916
-164
lines changed

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ num-derive = "0.4.2"
7777
p256 = "0.13.2"
7878
p384 = "0.13.0"
7979
pretty_assertions = "1.4.0"
80-
protobuf = "3.5.1"
81-
protobuf-codegen = "3.5.1"
82-
protobuf-json-mapping = "3.5.1"
83-
protobuf-parse = "3.5.1"
84-
protobuf-support = "3.5.1"
80+
protobuf = "3.7.1"
81+
protobuf-codegen = "3.7.1"
82+
protobuf-json-mapping = "3.7.1"
83+
protobuf-parse = "3.7.1"
84+
protobuf-support = "3.7.1"
8585
quanta = "0.12.3"
8686
rayon = "1.10.0"
8787
regex-syntax = "0.8.4"

capi/include/yara_x.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,56 @@ enum YRX_RESULT yrx_compiler_add_source_with_origin(struct YRX_COMPILER *compile
273273
enum YRX_RESULT yrx_compiler_ignore_module(struct YRX_COMPILER *compiler,
274274
const char *module);
275275

276+
// Enables a feature on this compiler.
277+
//
278+
// When defining the structure of a module in a `.proto` file, you can
279+
// specify that certain fields are accessible only when one or more
280+
// features are enabled. For example, the snippet below shows the
281+
// definition of a field named `requires_foo_and_bar`, which can be
282+
// accessed only when both features "foo" and "bar" are enabled.
283+
//
284+
// ```protobuf
285+
// optional uint64 requires_foo_and_bar = 500 [
286+
// (yara.field_options) = {
287+
// acl: [
288+
// {
289+
// allow_if: "foo",
290+
// error_title: "foo is required",
291+
// error_label: "this field was used without foo"
292+
// },
293+
// {
294+
// allow_if: "bar",
295+
// error_title: "bar is required",
296+
// error_label: "this field was used without bar"
297+
// }
298+
// ]
299+
// }
300+
// ];
301+
// ```
302+
//
303+
// If some of the required features are not enabled, using this field in
304+
// a YARA rule will cause an error while compiling the rules. The error
305+
// looks like:
306+
//
307+
// ```text
308+
// error[E034]: foo is required
309+
// --> line:5:29
310+
// |
311+
// 5 | test_proto2.requires_foo_and_bar == 0
312+
// | ^^^^^^^^^^^^^^^^^^^^ this field was used without foo
313+
// |
314+
// ```
315+
//
316+
// Notice that both the title and label in the error message are defined
317+
// in the .proto file.
318+
//
319+
// # Important
320+
//
321+
// This API is hidden from the public documentation because it is unstable
322+
// and subject to change.
323+
enum YRX_RESULT yrx_compiler_enable_feature(struct YRX_COMPILER *compiler,
324+
const char *feature);
325+
276326
// Tell the compiler that a YARA module can't be used.
277327
//
278328
// Import statements for the banned module will cause an error. The error

capi/src/compiler.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,75 @@ pub unsafe extern "C" fn yrx_compiler_ignore_module(
159159
YRX_RESULT::SUCCESS
160160
}
161161

162+
/// Enables a feature on this compiler.
163+
///
164+
/// When defining the structure of a module in a `.proto` file, you can
165+
/// specify that certain fields are accessible only when one or more
166+
/// features are enabled. For example, the snippet below shows the
167+
/// definition of a field named `requires_foo_and_bar`, which can be
168+
/// accessed only when both features "foo" and "bar" are enabled.
169+
///
170+
/// ```protobuf
171+
/// optional uint64 requires_foo_and_bar = 500 [
172+
/// (yara.field_options) = {
173+
/// acl: [
174+
/// {
175+
/// allow_if: "foo",
176+
/// error_title: "foo is required",
177+
/// error_label: "this field was used without foo"
178+
/// },
179+
/// {
180+
/// allow_if: "bar",
181+
/// error_title: "bar is required",
182+
/// error_label: "this field was used without bar"
183+
/// }
184+
/// ]
185+
/// }
186+
/// ];
187+
/// ```
188+
///
189+
/// If some of the required features are not enabled, using this field in
190+
/// a YARA rule will cause an error while compiling the rules. The error
191+
/// looks like:
192+
///
193+
/// ```text
194+
/// error[E034]: foo is required
195+
/// --> line:5:29
196+
/// |
197+
/// 5 | test_proto2.requires_foo_and_bar == 0
198+
/// | ^^^^^^^^^^^^^^^^^^^^ this field was used without foo
199+
/// |
200+
/// ```
201+
///
202+
/// Notice that both the title and label in the error message are defined
203+
/// in the .proto file.
204+
///
205+
/// # Important
206+
///
207+
/// This API is hidden from the public documentation because it is unstable
208+
/// and subject to change.
209+
#[no_mangle]
210+
pub unsafe extern "C" fn yrx_compiler_enable_feature(
211+
compiler: *mut YRX_COMPILER,
212+
feature: *const c_char,
213+
) -> YRX_RESULT {
214+
let compiler = if let Some(compiler) = compiler.as_mut() {
215+
compiler
216+
} else {
217+
return YRX_RESULT::INVALID_ARGUMENT;
218+
};
219+
220+
let feature = if let Ok(module) = CStr::from_ptr(feature).to_str() {
221+
module
222+
} else {
223+
return YRX_RESULT::INVALID_ARGUMENT;
224+
};
225+
226+
compiler.inner.enable_feature(feature);
227+
228+
YRX_RESULT::SUCCESS
229+
}
230+
162231
/// Tell the compiler that a YARA module can't be used.
163232
///
164233
/// Import statements for the banned module will cause an error. The error

capi/src/tests.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::compiler::{
33
yrx_compiler_build, yrx_compiler_create, yrx_compiler_define_global_bool,
44
yrx_compiler_define_global_float, yrx_compiler_define_global_int,
55
yrx_compiler_define_global_str, yrx_compiler_destroy,
6-
yrx_compiler_new_namespace,
6+
yrx_compiler_enable_feature, yrx_compiler_new_namespace,
77
};
88
use crate::{
99
yrx_buffer_destroy, yrx_last_error, yrx_rule_identifier,
@@ -139,6 +139,9 @@ fn capi() {
139139
some_str.as_ptr(),
140140
);
141141

142+
let feature = CString::new(b"foo").unwrap();
143+
yrx_compiler_enable_feature(compiler, feature.as_ptr());
144+
142145
let namespace = CString::new(b"foo").unwrap();
143146
yrx_compiler_new_namespace(compiler, namespace.as_ptr());
144147
yrx_compiler_add_source(compiler, src.as_ptr());

cli/src/commands/scan.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ pub fn scan() -> Command {
178178
.help("Abort scanning after the given number of seconds")
179179
.value_parser(value_parser!(u64).range(1..))
180180
)
181+
181182
}
182183

183184
#[cfg(feature = "rules-profiling")]

go/compiler.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ func IgnoreModule(module string) CompileOption {
5151
}
5252
}
5353

54+
// WithFeature enables a feature while compiling rules.
55+
//
56+
// NOTE: This API is still experimental and subject to change.
57+
func WithFeature(feature string) CompileOption {
58+
return func(c *Compiler) error {
59+
c.features = append(c.features, feature)
60+
return nil
61+
}
62+
}
63+
5464
// BanModule is an option for [NewCompiler] and [Compile] that allows
5565
// banning the use of a given module.
5666
//
@@ -223,6 +233,7 @@ type Compiler struct {
223233
ignoredModules map[string]bool
224234
bannedModules map[string]bannedModule
225235
vars map[string]interface{}
236+
features []string
226237
}
227238

228239
// NewCompiler creates a new compiler.
@@ -231,6 +242,7 @@ func NewCompiler(opts ...CompileOption) (*Compiler, error) {
231242
ignoredModules: make(map[string]bool),
232243
bannedModules: make(map[string]bannedModule),
233244
vars: make(map[string]interface{}),
245+
features: make([]string, 0),
234246
}
235247

236248
for _, opt := range opts {
@@ -266,6 +278,9 @@ func (c *Compiler) initialize() error {
266278
for name, _ := range c.ignoredModules {
267279
c.ignoreModule(name)
268280
}
281+
for _, feature := range c.features {
282+
c.enableFeature(feature)
283+
}
269284
for name, v := range c.bannedModules {
270285
c.banModule(name, v.errTitle, v.errMsg)
271286
}
@@ -335,6 +350,18 @@ func (c *Compiler) AddSource(src string, opts ...SourceOption) error {
335350
return nil
336351
}
337352

353+
// enableFeature enables a compiler feature.
354+
// See: [WithFeature].
355+
func (c *Compiler) enableFeature(feature string) {
356+
cFeature := C.CString(feature)
357+
defer C.free(unsafe.Pointer(cFeature))
358+
result := C.yrx_compiler_enable_feature(c.cCompiler, cFeature)
359+
if result != C.SUCCESS {
360+
panic("yrx_compiler_enable_feature failed")
361+
}
362+
runtime.KeepAlive(c)
363+
}
364+
338365
// ignoreModule tells the compiler to ignore the module with the given name.
339366
//
340367
// Any YARA rule using the module will be ignored, as well as rules that depends

go/compiler_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,29 @@ func TestError(t *testing.T) {
145145
assert.EqualError(t, err, expected)
146146
}
147147

148+
func TestCompilerFeatures(t *testing.T) {
149+
rules := `import "test_proto2" rule test { condition: test_proto2.requires_foo_and_bar }`
150+
151+
_, err := Compile(rules)
152+
assert.EqualError(t, err, `error[E100]: foo is required
153+
--> line:1:57
154+
|
155+
1 | import "test_proto2" rule test { condition: test_proto2.requires_foo_and_bar }
156+
| ^^^^^^^^^^^^^^^^^^^^ this field was used without foo
157+
|`)
158+
159+
_, err = Compile(rules, WithFeature("foo"))
160+
assert.EqualError(t, err, `error[E100]: bar is required
161+
--> line:1:57
162+
|
163+
1 | import "test_proto2" rule test { condition: test_proto2.requires_foo_and_bar }
164+
| ^^^^^^^^^^^^^^^^^^^^ this field was used without bar
165+
|`)
166+
167+
_, err = Compile(rules, WithFeature("foo"), WithFeature("bar"))
168+
assert.NoError(t, err)
169+
}
170+
148171
func TestErrors(t *testing.T) {
149172
c, err := NewCompiler()
150173
assert.NoError(t, err)

go/main.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,19 +208,6 @@ func (r *Rules) Destroy() {
208208
runtime.SetFinalizer(r, nil)
209209
}
210210

211-
// This is the callback called by yrx_rules_iterate, when Rules.GetRules is
212-
// called.
213-
//
214-
//export onRule
215-
func onRule(rule *C.YRX_RULE, handle C.uintptr_t) {
216-
h := cgo.Handle(handle)
217-
rules, ok := h.Value().(*[]*Rule)
218-
if !ok {
219-
panic("onRule didn't receive a *[]Rule")
220-
}
221-
*rules = append(*rules, newRule(rule))
222-
}
223-
224211
// Slice returns a slice with all the individual rules contained in this
225212
// set of compiled rules.
226213
func (r *Rules) Slice() []*Rule {

lib/src/compiler/context.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use itertools::Itertools;
21
use std::mem::size_of;
32
use std::rc::Rc;
43

4+
use itertools::Itertools;
5+
use rustc_hash::FxHashSet;
6+
57
use yara_x_parser::ast::{Ident, WithSpan};
68

79
use crate::compiler::errors::{CompileError, UnknownPattern};
@@ -41,6 +43,9 @@ pub(crate) struct CompileContext<'a, 'src, 'sym> {
4143
/// Warnings generated during the compilation.
4244
pub warnings: &'a mut Warnings,
4345

46+
/// Enabled features. See [`crate::Compiler::enable_feature`] for details.
47+
pub features: &'a FxHashSet<String>,
48+
4449
/// Stack of variables. These are local variables used during the
4550
/// evaluation of rule conditions, for example for storing loop variables.
4651
pub vars: VarStack,

0 commit comments

Comments
 (0)