Skip to content

Commit c0fa0bf

Browse files
authored
Edhoedt callback on include (#749)
* Full structure implemented, callback available. TODO: actually use the callback's result to import rules files. * Importing files via external callback fully implemented and working * Removing useless comments * Adding documentation * Updating doc for include callbacks * Fixing compiler_set_include_callback's signature * Fixing issues according to @plusvic 's advice * Limited lines to 80 chars * include callback is now using its own user_data and does not conflict with other callbacks * Copying include callback result to the stack before pushing it into the flex scanner * yara's memory management functions are now exposed through the API * Making the lexer reponsible for freeing the memory allocated by the include callback * Updating docs accordingly * * fixing memory management issues in callbacks * adding a callback to allow external code to free the memory used to return the include callback's result * API: un-exposing yara's memory management functions * making include callbacks the only way ton include files * adding a default include callback to open files from the disk (mimicking original behavior) * updating docs accordingly * fixing missing pointer cast * re-generating lexer.c * removing imports ununsed since commit 92877db * fixing default file read callback * Fixing line numbers in errors * Fix some issues related to includes callback. * Remove allow_includes field from YR_COMPILER. Includes now can be disallowed by setting the include callback to NULL. * Fix memory and file handle leaks. * Calculate size of error message buffer correctly. * Add test case.
1 parent e5629b7 commit c0fa0bf

File tree

7 files changed

+374
-223
lines changed

7 files changed

+374
-223
lines changed

docs/capi.rst

+44
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,40 @@ contains the file name and line number where the error or warning occurs.
6363
you're using :c:func:`yr_compiler_add_string`. The ``user_data`` pointer is the
6464
same you passed to :c:func:`yr_compiler_set_callback`.
6565

66+
By default, for rules containing references to other files
67+
(``include "filename.yara"``), yara will try to find those files on disk.
68+
However, if you want to fetch the imported rules from another source (eg: from a
69+
database or remote service), a callback function can be set with
70+
:c:func:`yr_compiler_set_include_callback`.
71+
The callback receives the following parameters:
72+
* ``include_name``: name of the requested file.
73+
* ``calling_rule_filename``: the requesting file name (NULL if not a file).
74+
* ``calling_rule_namespace``: namespace (NULL if undefined).
75+
* ``user_data`` pointer is the same you passed to :c:func:`yr_compiler_set_include_callback`.
76+
It should return the requested file's content as a string. The memory for this
77+
string should be allocated by the callback function. Once it is safe to free the
78+
memory used to return the callback's result, the include_free function passed to
79+
:c:func:`yr_compiler_set_include_callback` will be called. If the memory does
80+
not need to be freed, NULL can be passed as include_free instead.
81+
82+
The callback function has the following prototype:
83+
84+
.. code-block:: c
85+
86+
const char* include_callback(
87+
const char* include_name,
88+
const char* calling_rule_filename,
89+
const char* calling_rule_namespace,
90+
void* user_data);
91+
92+
The free function has the following prototype:
93+
94+
.. code-block:: c
95+
96+
void include_free(
97+
const char* callback_result_ptr,
98+
void* user_data);
99+
66100
After you successfully added some sources you can get the compiled rules
67101
using the :c:func:`yr_compiler_get_rules()` function. You'll get a pointer to
68102
a :c:type:`YR_RULES` structure which can be used to scan your data as
@@ -402,6 +436,15 @@ Functions
402436
pointer is passed to the callback function.
403437
404438
439+
.. c:function:: void yr_compiler_set_include_callback(YR_COMPILER* compiler, YR_COMPILER_INCLUDE_CALLBACK_FUNC callback, YR_COMPILER_INCLUDE_FREE_FUNC include_free, void* user_data)
440+
441+
Set a callback to provide rules from a custom source when ``include``
442+
directive is invoked. The *user_data* pointer is untouched and passed back to
443+
the callback function and to the free function. Once the callback's result
444+
is no longer needed, the include_free function will be called. If the memory
445+
does not need to be freed, include_free can be set to NULL.
446+
447+
405448
.. c:function:: int yr_compiler_add_file(YR_COMPILER* compiler, FILE* file, const char* namespace, const char* file_name)
406449
407450
Compile rules from a *file*. Rules are put into the specified *namespace*,
@@ -668,6 +711,7 @@ Functions
668711
Enables the specified rule. After being disabled with :c:func:`yr_rule_disable`
669712
a rule can be enabled again by using this function.
670713

714+
671715
Error codes
672716
-----------
673717

docs/yarapython.rst

+30
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,36 @@ should be accepted in the source files, for example:
7474
If the source file contains include directives the previous line would raise
7575
an exception.
7676

77+
If includes are used, a python callback can be set to define a custom source for
78+
the imported files (by default they are read from disk). This callback function
79+
is set through the ``include_callback`` optional parameter.
80+
It receives the following parameters:
81+
*``requested_filename``: file requested with 'include'
82+
*``filename``: file containing the 'include' directive if applicable, else None
83+
*``namespace``: namespace
84+
And returns the requested rules sources as a single string.
85+
86+
.. code-block:: python
87+
import yara
88+
import sys
89+
if sys.version_info >= (3, 0):
90+
import urllib.request as urllib
91+
else:
92+
import urllib as urllib
93+
94+
def mycallback(requested_filename, filename, namespace):
95+
if requested_filename == 'req.yara':
96+
uf = urllib.urlopen('https://pastebin.com/raw/siZ2sMTM')
97+
sources = uf.read()
98+
if sys.version_info >= (3, 0):
99+
sources = str(sources, 'utf-8')
100+
return sources
101+
else:
102+
raise Exception(filename+": Can't fetch "+requested_filename)
103+
104+
rules = yara.compile(source='include "req.yara" rule r{ condition: true }',
105+
include_callback=mycallback)
106+
77107
If you are using external variables in your rules you must define those
78108
external variables either while compiling the rules, or while applying the
79109
rules to some file. To define your variables at the moment of compilation you

libyara/compiler.c

+107-19
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ YR_API int yr_compiler_create(
5656

5757
new_compiler->errors = 0;
5858
new_compiler->callback = NULL;
59+
new_compiler->include_callback = _yr_compiler_default_include_callback;
60+
new_compiler->include_free = _yr_compiler_default_include_free;
5961
new_compiler->last_error = ERROR_SUCCESS;
6062
new_compiler->last_error_line = 0;
6163
new_compiler->current_line = 0;
6264
new_compiler->last_result = ERROR_SUCCESS;
63-
new_compiler->file_stack_ptr = 0;
6465
new_compiler->file_name_stack_ptr = 0;
6566
new_compiler->fixup_stack_head = NULL;
66-
new_compiler->allow_includes = 1;
6767
new_compiler->loop_depth = 0;
6868
new_compiler->loop_for_of_mem_offset = -1;
6969
new_compiler->compiled_rules_arena = NULL;
@@ -182,38 +182,126 @@ YR_API void yr_compiler_set_callback(
182182
}
183183

184184

185-
int _yr_compiler_push_file(
186-
YR_COMPILER* compiler,
187-
FILE* fh)
185+
const char* _yr_compiler_default_include_callback(
186+
const char* include_name,
187+
const char* calling_rule_filename,
188+
const char* calling_rule_namespace,
189+
void* user_data)
188190
{
189-
if (compiler->file_stack_ptr < MAX_INCLUDE_DEPTH)
191+
char* buffer;
192+
char* s = NULL;
193+
#ifdef _WIN32
194+
char* b = NULL;
195+
#endif
196+
char* f;
197+
FILE* fh;
198+
char* file_buffer;
199+
size_t file_length;
200+
201+
if (calling_rule_filename != NULL)
190202
{
191-
compiler->file_stack[compiler->file_stack_ptr] = fh;
192-
compiler->file_stack_ptr++;
193-
return ERROR_SUCCESS;
203+
buffer = (char*) calling_rule_filename;
194204
}
195205
else
196206
{
197-
compiler->last_result = ERROR_INCLUDE_DEPTH_EXCEEDED;
198-
return ERROR_INCLUDE_DEPTH_EXCEEDED;
207+
buffer = "\0";
208+
}
209+
210+
s = strrchr(buffer, '/');
211+
212+
#ifdef _WIN32
213+
b = strrchr(buffer, '\\'); // in Windows both path delimiters are accepted
214+
#endif
215+
216+
#ifdef _WIN32
217+
if (s != NULL || b != NULL)
218+
#else
219+
if (s != NULL)
220+
#endif
221+
{
222+
#ifdef _WIN32
223+
f = (b > s)? (b + 1): (s + 1);
224+
#else
225+
f = s + 1;
226+
#endif
227+
228+
strlcpy(f, include_name, sizeof(buffer) - (f - buffer));
229+
230+
f = buffer;
231+
// SECURITY: Potential for directory traversal here.
232+
fh = fopen(buffer, "rb");
233+
234+
// if include file was not found relative to current source file,
235+
// try to open it with path as specified by user (maybe user wrote
236+
// a full path)
237+
if (fh == NULL)
238+
{
239+
f = (char*) include_name;
240+
// SECURITY: Potential for directory traversal here.
241+
fh = fopen(include_name, "rb");
242+
}
243+
}
244+
else
245+
{
246+
f = (char*) include_name;
247+
// SECURITY: Potential for directory traversal here.
248+
fh = fopen(include_name, "rb");
249+
}
250+
251+
if (fh == NULL)
252+
return NULL;
253+
254+
fseek(fh, 0, SEEK_END);
255+
file_length = ftell(fh);
256+
fseek(fh, 0, SEEK_SET);
257+
258+
file_buffer = (char*) yr_malloc(file_length + 1);
259+
260+
if (file_buffer == NULL)
261+
{
262+
fclose(fh);
263+
return NULL;
264+
}
265+
266+
if (file_length != fread(file_buffer, 1, file_length, fh))
267+
{
268+
yr_free(file_buffer);
269+
fclose(fh);
270+
return NULL;
271+
}
272+
else
273+
{
274+
file_buffer[file_length]='\0';
199275
}
276+
277+
fclose(fh);
278+
return file_buffer;
200279
}
201280

202281

203-
FILE* _yr_compiler_pop_file(
204-
YR_COMPILER* compiler)
282+
void _yr_compiler_default_include_free(
283+
const char* callback_result_ptr,
284+
void* user_data)
205285
{
206-
FILE* result = NULL;
207-
208-
if (compiler->file_stack_ptr > 0)
286+
if(callback_result_ptr != NULL)
209287
{
210-
compiler->file_stack_ptr--;
211-
result = compiler->file_stack[compiler->file_stack_ptr];
288+
yr_free((void*)callback_result_ptr);
212289
}
290+
}
213291

214-
return result;
292+
293+
YR_API void yr_compiler_set_include_callback(
294+
YR_COMPILER* compiler,
295+
YR_COMPILER_INCLUDE_CALLBACK_FUNC include_callback,
296+
YR_COMPILER_INCLUDE_FREE_FUNC include_free,
297+
void* user_data)
298+
{
299+
compiler->include_callback = include_callback;
300+
compiler->include_free = include_free;
301+
compiler->incl_clbk_user_data = user_data;
215302
}
216303

304+
217305
int _yr_compiler_push_file_name(
218306
YR_COMPILER* compiler,
219307
const char* file_name)

libyara/include/yara/compiler.h

+31-13
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ typedef void (*YR_COMPILER_CALLBACK_FUNC)(
5252
void* user_data);
5353

5454

55+
typedef const char* (*YR_COMPILER_INCLUDE_CALLBACK_FUNC)(
56+
const char* include_name,
57+
const char* calling_rule_filename,
58+
const char* calling_rule_namespace,
59+
void* user_data);
60+
61+
typedef void (*YR_COMPILER_INCLUDE_FREE_FUNC)(
62+
const char* callback_result_ptr,
63+
void* user_data );
64+
65+
5566
typedef struct _YR_FIXUP
5667
{
5768
void* address;
@@ -98,14 +109,9 @@ typedef struct _YR_COMPILER
98109
int loop_depth;
99110
int loop_for_of_mem_offset;
100111

101-
int allow_includes;
102-
103112
char* file_name_stack[MAX_INCLUDE_DEPTH];
104113
int file_name_stack_ptr;
105114

106-
FILE* file_stack[MAX_INCLUDE_DEPTH];
107-
int file_stack_ptr;
108-
109115
char last_error_extra_info[MAX_COMPILER_ERROR_EXTRA_INFO];
110116

111117
char lex_buf[LEX_BUF_SIZE];
@@ -114,8 +120,12 @@ typedef struct _YR_COMPILER
114120

115121
char include_base_dir[MAX_PATH];
116122
void* user_data;
123+
void* incl_clbk_user_data;
117124

118125
YR_COMPILER_CALLBACK_FUNC callback;
126+
YR_COMPILER_INCLUDE_CALLBACK_FUNC include_callback;
127+
YR_COMPILER_INCLUDE_FREE_FUNC include_free;
128+
119129

120130
} YR_COMPILER;
121131

@@ -134,14 +144,6 @@ typedef struct _YR_COMPILER
134144
fmt, __VA_ARGS__);
135145

136146

137-
int _yr_compiler_push_file(
138-
YR_COMPILER* compiler,
139-
FILE* fh);
140-
141-
142-
FILE* _yr_compiler_pop_file(
143-
YR_COMPILER* compiler);
144-
145147

146148
int _yr_compiler_push_file_name(
147149
YR_COMPILER* compiler,
@@ -151,6 +153,15 @@ int _yr_compiler_push_file_name(
151153
void _yr_compiler_pop_file_name(
152154
YR_COMPILER* compiler);
153155

156+
const char* _yr_compiler_default_include_callback(
157+
const char* include_name,
158+
const char* calling_rule_filename,
159+
const char* calling_rule_namespace,
160+
void* user_data);
161+
162+
void _yr_compiler_default_include_free(
163+
const char* callback_result_ptr,
164+
void* user_data);
154165

155166
YR_API int yr_compiler_create(
156167
YR_COMPILER** compiler);
@@ -166,6 +177,13 @@ YR_API void yr_compiler_set_callback(
166177
void* user_data);
167178

168179

180+
YR_API void yr_compiler_set_include_callback(
181+
YR_COMPILER* compiler,
182+
YR_COMPILER_INCLUDE_CALLBACK_FUNC include_callback,
183+
YR_COMPILER_INCLUDE_FREE_FUNC include_free,
184+
void* user_data);
185+
186+
169187
YR_API int yr_compiler_add_file(
170188
YR_COMPILER* compiler,
171189
FILE* rules_file,

0 commit comments

Comments
 (0)