YR_MAJOR_VERSION, YR_MINOR_VERSION and YR_MICRO_VERSION are fixed when the
caller is compiled, so a program linked against a shared libyara has no way
to tell which version it actually loaded. Distros ship libyara.so in its own
package precisely so it can be updated independently of its consumers, which
is exactly the case the macros cannot describe.
Add yr_get_version(), which reports the version of the library that is
actually loaded. Any argument may be NULL if the caller does not need that
component, and since the function touches no global state it can be called
before yr_initialize.
Closes#2136
---------
Co-authored-by: Victor M. Alvarez <[email protected]>
elf.machine can hold any value from the ELF header, but the module only
named 14 of them, so a rule targeting anything else had to hardcode the
number or drop the module and match the header bytes by hand.
Add the architectures from the issue: PARISC, SPARC32PLUS, S390, MCORE,
SH, SPARCV9, ARC_COMPACT, BPF and LOONGARCH.
0x27 is exposed under both EM_MCORE and EM_RCE. EM_RCE is the name it was
originally registered under and still what some toolchains emit, so a rule
written against either name works.
Closes#2149
Ensure `YR_ARENA` objects are created, loaded, and accessed with a valid number of buffers. This prevents out-of-bounds memory access and improves robustness against corrupted arena data or incorrect API usage.
Closes#2238.
The hex string and regexp lexers parse jump lengths and repeat intervals
with atoi, which is undefined for numbers that don't fit in an int. glibc
hands back the low 32 bits, so a value that wraps to a small positive
number passes every bounds check and silently changes the pattern:
rule r1 { strings: $a = /a{4294967296}/ condition: $a }
rule r2 { strings: $b = { 61 [0-4294967297] 62 } condition: $b }
Before this change r1 compiled as a{0} and matched at every offset, and
r2 compiled as { 61 [0-1] 62 }. MSVC saturates atoi at INT_MAX instead,
so the same rule behaves differently there, which is what #1791 reports.
Parse with strtoll instead. Numbers too large for a long long are clamped
to LLONG_MAX, which still fails the range check, so "repeat interval too
large" now fires for everything above RE_MAX_RANGE on any platform. Hex
jumps get an explicit INT_MAX check because RE_NODE stores start and end
as ints, and the grammar already casts to int.
The hi_bound < 0 and lo_bound < 0 checks are gone. They were there to
catch atoi wrapping negative and are unreachable now that the only thing
the pattern matches is digits.
Fixes#1791.
validate the aho-corasick transition and match tables when loading, otherwise a crafted .yrc whose next-state or match-pool index points past the end of the tables makes the per-byte scan loop in _yr_scanner_scan_mem_block read out of bounds.
The RE_OPCODE_REPEAT_END handler in _yr_re_fiber_sync does `fiber->stack[fiber->sp]++` without checking that sp is not negative:
- sp starts at -1 and only a preceding REPEAT_START advances it to 0
- a hand-crafted .yrc can reach REPEAT_END with no matching REPEAT_START, so `stack[-1]++` writes into the fiber's `next` pointer and the later fiber traversal follows the corrupted value
- the REPEAT_START push already carries the paranoid upper-bound guard, only this lower bound was missing
Added the sp < 0 guard under YR_PARANOID_EXEC. A regexp of a single REPEAT_END opcode gives an ASAN SEGV in _yr_re_fiber_split before the patch and a clean ERROR_INTERNAL_FATAL_ERROR after; regression test added in test-re-split.c.
conditional-jump opcodes add an unchecked 4-byte offset from the compiled bytecode to the instruction pointer, so a crafted .yrc whose taken jump lands past the code section makes yr_execute_code fetch the next opcode out of bounds; reject any ip that leaves the code buffer.
**Zero-sized allocation for an unknown object type in yr_object_create**
The external variable types stored in a compiled rules file are never validated, so a crafted .yrc reaches yr_object_create with a type that matches no case and leaves object_size at 0, and the field writes right after the allocation land past the end of it. Both switches guarded this with assert only, which is compiled out under NDEBUG the way the projects in windows/ set it.
the digest carried in a signature can be shorter than the digest algorithm's output size, so the memcmp in parse_authenticode and pkcs9_countersig_new reads past the stored digest; compare the lengths first, the way countersignature.c already does at its other three digest compares.
Rule index read from compiled bytecode is used to index rules_table without a bounds check:
- OP_PUSH_RULE dereferences rules_table[r1.i] and indexes rule_matches_flags with the same value, unchecked
- OP_INIT_RULE only had an assert, which is a no-op under NDEBUG
- OP_MATCH_RULE right below them already guards the index with ensure_within_rules_arena
Added the same YR_PARANOID_EXEC guard to both. A crafted .yrc with an out-of-range rule index reaches this through yr_rules_scan_mem and reads past the table; test-exec covers it.
Unbounded push onto the per-fiber repeat stack in _yr_re_fiber_sync:
- REPEAT_START does stack[++sp] = 0 with sp never checked against RE_MAX_STACK
- a crafted rule with a run of more than RE_MAX_STACK REPEAT_START opcodes writes past the 1024-entry stack at the tail of the heap RE_FIBER
- confirmed under ASAN: heap-buffer-overflow write at re.c:1560 reached through yr_re_exec
Guarded under YR_PARANOID_EXEC like the split id check above, with a regression test.
Loading a compiled rules file trusts the counts in its summary:
- num_rules drives the rules_table walk in yr_rules_from_arena, so an inflated count reads past YR_RULES_TABLE (ASAN heap-buffer-overflow at rules.c:381, reached through yr_rules_load)
- num_strings and num_namespaces feed scanner-side allocations and index math with the same gap
Reject any count larger than its table's loaded size with ERROR_CORRUPT_FILE. The added test saves a small ruleset, inflates num_rules in the summary, and checks the load is rejected.
The set iterators in yr_execute_code read the enumeration item count from the compiled rules and use it unchecked:
- int_enum sizes the iterator allocation with it and then pops that many values off the stack
- string_set and text_string_set repeat the same pattern
- a .yrc loaded through yr_rules_load can carry a count past the stack depth, so the allocation is undersized and the loop runs off the value stack
Bounded each count against the current stack depth. test-iterators saves `for any i in (1,2,3)`, bumps the stored count from 3 to 255 and scans: ASAN reports an out-of-bounds read in yr_execute_code before the change, clean after.
Missing byte swap in the ELF symtab/dynsym link check
The SHT_SYMTAB and SHT_DYNSYM string table check compares sh_link against the raw e_shnum (sh_entry_count) while every other use in the macro byte-swaps it, so on a little-endian host reading a big-endian ELF the check accepts a link past the real section count and parses the symbol string table from an out-of-range section header. Swapped it to match the sibling reads, and added a regression test scanning a crafted big-endian ELF whose only symtab carries an out-of-range sh_link.
bound the relocation target ref's buffer_id and offset in yr_arena_load_stream before yr_arena_ref_to_ptr converts it, otherwise a crafted compiled rules file supplies a target buffer_id past the 16-slot buffers array that yr_arena_get_ptr only guards with assert, which is compiled out under NDEBUG and reads out of bounds.
Constant folding in `grammar.y` hits UB at the `INT64_MIN` boundary, reachable from rule text:
- `*`: `llabs(INT64_MIN)` is UB and returns a negative value, so the overflow guard is bypassed and folds to a garbage constant instead of `ERROR_INTEGER_OVERFLOW`. Now compares magnitudes in unsigned space.
- `\`: `INT64_MIN / -1` is overflow UB and raises SIGFPE on x86. Now rejected with `ERROR_INTEGER_OVERFLOW`.
- unary `-`: `-INT64_MIN` is negation UB. Now negated in unsigned space (wraps to `INT64_MIN`, unchanged behavior).
Reproduce `condition` expr (each reported an UBSan error):
`~9223372036854775807 * 9223372036854775807`
`~9223372036854775807 \ -1`
`-(~9223372036854775807)`
Example Logs:
```
libyara/grammar.y:2813:30: runtime error: negation of -9223372036854775808 cannot be represented in type 'int64_t' (aka 'long'); cast to an unsigned type to negate this value to itself
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior libyara/grammar.y:2813:30
```
Note: The VM ops in `exec.c` and the `%` rule likely share this UB.
Disclose: assisted with AI
noticed the relocation loop in yr_arena_load_stream reads b->data before it checks reloc_ref.buffer_id against num_buffers. the id comes straight from the loaded stream, so a crafted reloc entry with a large buffer_id indexes far past the 16-slot buffers array and reads out of bounds. moving the range check ahead of the dereference rejects it as corrupt first. looks like the check order got flipped in #2178. added a regression test in test-arena.c.
When the backward scan in pe_parse_rich_signature finds a DanS marker only one to three dwords ahead of the Rich marker, rich_len ends up smaller than RICH_SIGNATURE. The unsigned subtraction rich_len - sizeof(RICH_SIGNATURE) then wraps and rich_count becomes a huge bogus value that drives the version_data allocation and copy loop. _rich_version already rejects rich_length < sizeof(RICH_SIGNATURE) for the same computation, so apply the same check before computing rich_count here.
Bazel was the build system used by VirusTotal. Now that VirusTotal has moved to YARA-X, maintaining the Bazel infrastructure working is painful, specially with the backward incompatible changes introduced in recent versions of Bazel.
In regular expressions, repetition operators`{0,0}` and `{,0}` are valid, but they always match the zero-length string. For instance, `a{0,0}` and `[a-z]{0,0}` both match the zero-length string.
When the whole regular expression consists in one of these repetitions it caused a segfault during the evaluation of the regular expression because the `forward_code_ref` field for the root atom in the atom's tree was null.
Closes#2084.
For instance, `x{n, m}`, `x{n ,m}` and `x{n , m}` are all equivalent to `x{n,m}`. This is done for compatibility with YARA-X, but I've seen real-life regular expressions in that contain such spaces, for example:
```
/C:\\Users\\qqq\\.{30, 50}\.pdb/
```
The `matches` operator was returning false if the left operand was an empty strings. So, `"" matches <some regexp>` was returning false regardless of the regexp used. However, some regexp *do* match the empty strings, like for example `/foo|/`.
This is backward compatible change that affects the `magic` field. After this change the value in the `magic` field looks exactly as it looks in the file regardless of the endianness of the current platform, if the file starts with `CA FE BA BE` the value in magic is `0xCAFEBABE`, not `0xBEBAFECA` as it used to be in little-endian architectures.
Regexps with quantifiers `{0}` and `{0,0}` were considered syntactically invalid. These quantifiers are not actually useful, but they are now allowed for compatibility with other regexp engines.
Closes#2037.
Until now, `pe.is_signed` was undefined for every PE file that was not signed, making impossible to use it in negated conditions, like `pe.is_pe and not pe.is_signed`. With this change, `pe.is_signed` is false for every PE file that is not signed.
Closes#2040
Some regexps like `/(abc|)/` where not matching the empty string even if they should. Matching an empty string is not very useful, as such regexp will match on every offset of every file, but this is fixed for correctness.
Also fixes another issue with the `\b` metacharacter in regexps, which was exposed after fixing the main issue.
PR #1927 didn't take into account that as a side-effect of skipping the evaluation of global rules, the corresponding namespace was not flagged as unsatisfied (i.e: a global rule that evaluates to false prevents all other rules in the namespace from evaluating to true). The issue was not caught by any test case because the test case for global rules were using rules that didn't required any strings to match, and therefore were unaffected by the changes introduced in #1927.
This change makes sure that any global rule that is skipped is considered false, and therefore its namespace is flagged as unsatisfied.
Closes#2022.
* feat: prevent race conditions on signal handler
If multiple threads were running in parallel (e.g. each using a
YR_SCANNER), race conditions could cause the original signal handler
to not be restored correctly.
Add a mutex that controls access to the (process wide) signal handler
to ensure that the YARA signal handler is only installed once and
only removed when it is no longer needed.
* feat: forward signal to old signal handler
If a signal is received that does not originate from YARA (which is
quite possible in multi-threaded applications using YARA), it is
currently ignored. Especially for SIGSEGV, this may cause unexpected
behaviour in the caller, even in threads that do not use YARA.
Add logic to call the original signal handler to handle this case
more cleanly. It is not perfect - with only a process wide signal
handler, it's probably impossible to have a perfect solution -
but it's better than the current behaviour.
* fix: make exception test more verbose
* fix: only handle signals in memory block data
Some callbacks from YARA code are done from YR_TRYCATCH. Currently,
YARA assumes that SIGBUS / SIGSEGV from these callbacks are due to
the scanned memory range, but this is not necessarily true.
Add logic to identify the currently scanned memory block and only
catch the signal if the SIGBUS / SIGSEGV originated from it.
* chore: simplify signal handler forwarding
* feat: reduce caught signals to required for known OS
When the file imported DLLs that contained a dot (.) in its name, like `glib-2.0.dll`, the file extension was not properly removed, causing the the imphash to be wrong.
A check in `pe_parse_debug_directory` was verifying that the size of the debug directory entry was a multiple of `sizeof(IMAGE_DEBUG_DIRECTORY)`. Although this is true in most cases, is not universally true for all PE files. This check was preventing the extraction of the PDB path from certain EFI boot files, like the ones below. The PDB path in such cases is not a file path but a GUID, though.
Some affected files:
`6c2abf4b80a87e63eee2996e5cea8f004d49ec0c1806080fa72e960529cba14c`
`5487a2ff41e360b1d3b0a58e393b0041c3f269f1053a3e367dfecd48e233457e`