diff --git a/cli/yara.c b/cli/yara.c index 8d05ee0f02..6827c70cd2 100644 --- a/cli/yara.c +++ b/cli/yara.c @@ -1225,6 +1225,28 @@ static int callback( return CALLBACK_CONTINUE; + case CALLBACK_MSG_TOO_SLOW_SCANNING: + if (ignore_warnings) + return CALLBACK_CONTINUE; + + string = (YR_STRING*) message_data; + rule = &context->rules->rules_table[string->rule_idx]; + + if (rule != NULL && string != NULL) + fprintf( + stderr, + "warning: rule \"%s\": scanning with string %s is taking a very long " + "time, it is either too general or very common.\n", + rule->identifier, + string->identifier); + else + return CALLBACK_CONTINUE; + + if (fail_on_warnings) + return CALLBACK_ERROR; + + return CALLBACK_CONTINUE; + case CALLBACK_MSG_TOO_MANY_MATCHES: if (ignore_warnings) return CALLBACK_CONTINUE; diff --git a/libyara/include/yara/error.h b/libyara/include/yara/error.h index aded7be362..0cd1ea6f39 100644 --- a/libyara/include/yara/error.h +++ b/libyara/include/yara/error.h @@ -107,6 +107,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define ERROR_INVALID_PERCENTAGE 62 #define ERROR_IDENTIFIER_MATCHES_WILDCARD 63 #define ERROR_INVALID_VALUE 64 +#define ERROR_TOO_SLOW_SCANNING 65 #define GOTO_EXIT_ON_ERROR(x) \ { \ diff --git a/libyara/include/yara/limits.h b/libyara/include/yara/limits.h index 714d66e252..7ef95495da 100644 --- a/libyara/include/yara/limits.h +++ b/libyara/include/yara/limits.h @@ -114,6 +114,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define YR_MAX_STRING_MATCHES 1000000 #endif +// The number of matches before detecting slow scanning. If more matches are found +// the scan will have a CALLBACK_MSG_TOO_SLOW_SCANNING. +#ifndef YR_SLOW_STRING_MATCHES +#define YR_SLOW_STRING_MATCHES 600000 +#endif + +// If size of the input is bigger then 0.2 MB and 0-length atoms are used +// the scan will have a CALLBACK_MSG_TOO_SLOW_SCANNING. +#ifndef YR_FILE_SIZE_THRESHOLD +#define YR_FILE_SIZE_THRESHOLD 200000 +#endif + // Maximum number of argument that a function in a YARA module can have. #ifndef YR_MAX_FUNCTION_ARGS #define YR_MAX_FUNCTION_ARGS 128 diff --git a/libyara/include/yara/rules.h b/libyara/include/yara/rules.h index 2147d4750f..2606e93c1e 100644 --- a/libyara/include/yara/rules.h +++ b/libyara/include/yara/rules.h @@ -42,6 +42,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define CALLBACK_MSG_MODULE_IMPORTED 5 #define CALLBACK_MSG_TOO_MANY_MATCHES 6 #define CALLBACK_MSG_CONSOLE_LOG 7 +#define CALLBACK_MSG_TOO_SLOW_SCANNING 8 #define CALLBACK_CONTINUE 0 #define CALLBACK_ABORT 1 diff --git a/libyara/scanner.c b/libyara/scanner.c index 5051d767d9..8e4d079e42 100644 --- a/libyara/scanner.c +++ b/libyara/scanner.c @@ -68,6 +68,8 @@ static int _yr_scanner_scan_mem_block( size_t i = 0; uint32_t state = YR_AC_ROOT_STATE; uint16_t index; + YR_STRING* report_string = NULL; + YR_RULE* rule = NULL; while (i < block->size) { @@ -104,6 +106,14 @@ static int _yr_scanner_scan_mem_block( match = &rules->ac_match_pool[match_table[state] - 1]; + if (scanner->matches->count >= YR_SLOW_STRING_MATCHES) + { + report_string = match->string; + rule = report_string + ? &scanner->rules->rules_table[report_string->rule_idx] + : NULL; + } + while (match != NULL) { if (match->backtrack <= i) @@ -162,6 +172,25 @@ static int _yr_scanner_scan_mem_block( } } + if (rule != NULL && + scanner->matches->count >= YR_SLOW_STRING_MATCHES && + scanner->matches->count < YR_MAX_STRING_MATCHES) + { + if (rule != NULL && report_string != NULL) + { + result = scanner->callback( + scanner, + CALLBACK_MSG_TOO_SLOW_SCANNING, + (void*) report_string, + scanner->user_data); + if (result != CALLBACK_CONTINUE) + { + result = ERROR_TOO_SLOW_SCANNING; + goto _exit; + } + } + } + _exit: YR_DEBUG_FPRINTF( @@ -655,6 +684,7 @@ YR_API int yr_scanner_scan_mem( YR_MEMORY_BLOCK block; YR_MEMORY_BLOCK_ITERATOR iterator; + int result; block.size = buffer_size; block.base = 0; @@ -667,7 +697,20 @@ YR_API int yr_scanner_scan_mem( iterator.file_size = _yr_get_file_size; iterator.last_error = ERROR_SUCCESS; - int result = yr_scanner_scan_mem_blocks(scanner, &iterator); + // Detect cases where every byte of input is checked for match and input size is bigger then 0.2 MB + if (scanner->rules->ac_match_table[YR_AC_ROOT_STATE] != 0 && buffer_size > YR_FILE_SIZE_THRESHOLD) + { + YR_STRING* report_string = scanner->rules->ac_match_pool[YR_AC_ROOT_STATE].string; + result = scanner->callback( + scanner, + CALLBACK_MSG_TOO_SLOW_SCANNING, + (void*) report_string, + scanner->user_data); + if (result != CALLBACK_CONTINUE) + return ERROR_TOO_SLOW_SCANNING; + } + + result = yr_scanner_scan_mem_blocks(scanner, &iterator); YR_DEBUG_FPRINTF( 2, diff --git a/tests/test-api.c b/tests/test-api.c index f6f1fa7dd3..7cb675ea81 100644 --- a/tests/test-api.c +++ b/tests/test-api.c @@ -394,6 +394,179 @@ void test_too_many_matches() yr_finalize(); } +int ignore_too_slow_scanning( + YR_SCAN_CONTEXT* context, + int message, + void* message_data, + void* user_data) +{ + return CALLBACK_CONTINUE; +} + +int propagate_too_slow_scanning( + YR_SCAN_CONTEXT* context, + int message, + void* message_data, + void* user_data) +{ + if (message == CALLBACK_MSG_TOO_SLOW_SCANNING) + return CALLBACK_ERROR; + + return CALLBACK_CONTINUE; +} + +void test_too_slow_scanning_slow_string_matches() +{ + YR_RULES* rules; + + char* rules_str = "\ + rule t { \ + strings: \ + $a = /a.{0,18}/ \ + condition: \ + $a \ + }"; + + yr_initialize(); + + if (compile_rule(rules_str, &rules) != ERROR_SUCCESS) + { + perror("compile_rule"); + exit(EXIT_FAILURE); + } + + uint8_t* buffer = (uint8_t*) malloc(YR_SLOW_STRING_MATCHES+100); + + if (buffer == NULL) + { + perror("malloc"); + exit(EXIT_FAILURE); + } + + memset(buffer, 'a', YR_SLOW_STRING_MATCHES+100); + + int err = yr_rules_scan_mem( + rules, + (const uint8_t*) buffer, + YR_SLOW_STRING_MATCHES+100, + 0, + propagate_too_slow_scanning, + NULL, + 0); + + if (err != ERROR_TOO_SLOW_SCANNING) + { + fprintf( + stderr, + "test_too_slow_scanning_slow_string_matches failed, expecting ERROR_TOO_SLOW_SCANNING, got " + "%d\n", + err); + + free(buffer); + exit(EXIT_FAILURE); + } + + err = yr_rules_scan_mem( + rules, + (const uint8_t*) buffer, + YR_SLOW_STRING_MATCHES+100, + 0, + ignore_too_slow_scanning, + NULL, + 0); + + if (err != ERROR_SUCCESS) + { + fprintf( + stderr, + "test_too_slow_scanning_slow_string_matches failed, expecting ERROR_SUCCESS, got %d\n", + err); + + free(buffer); + exit(EXIT_FAILURE); + } + + free(buffer); + yr_rules_destroy(rules); + yr_finalize(); +} + +void test_too_slow_scanning() +{ + YR_RULES* rules; + + char* rules_str = "\ + rule t { \ + strings: \ + $a = /\\w+/ \ + condition: \ + $a \ + }"; + + yr_initialize(); + + if (compile_rule(rules_str, &rules) != ERROR_SUCCESS) + { + perror("compile_rule"); + exit(EXIT_FAILURE); + } + + uint8_t* buffer = (uint8_t*) malloc(2 * YR_FILE_SIZE_THRESHOLD); + + if (buffer == NULL) + { + perror("malloc"); + exit(EXIT_FAILURE); + } + + memset(buffer, 'a', 2 * YR_FILE_SIZE_THRESHOLD); + + int err = yr_rules_scan_mem( + rules, + (const uint8_t*) buffer, + 2 * YR_FILE_SIZE_THRESHOLD, + 0, + propagate_too_slow_scanning, + NULL, + 0); + + if (err != ERROR_TOO_SLOW_SCANNING) + { + fprintf( + stderr, + "test_too_slow_scanning failed, expecting ERROR_TOO_SLOW_SCANNING, got " + "%d\n", + err); + + free(buffer); + exit(EXIT_FAILURE); + } + + err = yr_rules_scan_mem( + rules, + (const uint8_t*) buffer, + 2 * CALLBACK_MSG_TOO_SLOW_SCANNING, + 0, + ignore_too_slow_scanning, + NULL, + 0); + + if (err != ERROR_SUCCESS) + { + fprintf( + stderr, + "test_too_slow_scanning failed, expecting ERROR_SUCCESS, got %d\n", + err); + + free(buffer); + exit(EXIT_FAILURE); + } + + free(buffer); + yr_rules_destroy(rules); + yr_finalize(); +} + void test_save_load_rules() { YR_COMPILER* compiler = NULL; @@ -1099,6 +1272,8 @@ int main(int argc, char** argv) test_max_string_per_rules(); test_max_match_data(); test_too_many_matches(); + test_too_slow_scanning(); + test_too_slow_scanning_slow_string_matches(); test_include_callback(); test_save_load_rules(); test_scanner();