Improvements to the ex_scan_callbacks.c program:
- Print the verdict enum variant names to be more explicit.
- Add the file_props callback (aka metadata JSON) with --gen-json option.
- Add a --debug option.
- Use '-' in option names instead of '_' to be consistent with other programs.
- Add option to disable allmatch, which I named --one-match. :)
Tests: Add ex_scan_callbacks test where --allmatch is disabled.
Verify that CL_VIRUS is returned when a match occurs.
I found a few bugs and inconsistencies from this test and went and fixed
them, and improved the clamav.h function comments as well.
Largely this resulted in cleanup in `cli_magic_scan()` to make sure we
don't accidentally overwrite the return code.
But it also meant making sure that callback functions which are supposed
to trust a file actually clear the evidence/verdict and don't return
CL_VIRUS.
| ... | ... |
@@ -1083,6 +1083,22 @@ const char *cl_error_t_to_string(cl_error_t clerror) |
| 1083 | 1083 |
} |
| 1084 | 1084 |
} |
| 1085 | 1085 |
|
| 1086 |
+const char *cl_verdict_t_to_string(cl_verdict_t verdict) |
|
| 1087 |
+{
|
|
| 1088 |
+ switch (verdict) {
|
|
| 1089 |
+ case CL_VERDICT_NOTHING_FOUND: |
|
| 1090 |
+ return "CL_VERDICT_NOTHING_FOUND"; |
|
| 1091 |
+ case CL_VERDICT_TRUSTED: |
|
| 1092 |
+ return "CL_VERDICT_TRUSTED"; |
|
| 1093 |
+ case CL_VERDICT_STRONG_INDICATOR: |
|
| 1094 |
+ return "CL_VERDICT_STRONG_INDICATOR"; |
|
| 1095 |
+ case CL_VERDICT_POTENTIALLY_UNWANTED: |
|
| 1096 |
+ return "CL_VERDICT_POTENTIALLY_UNWANTED"; |
|
| 1097 |
+ default: |
|
| 1098 |
+ return "Unknown verdict value"; |
|
| 1099 |
+ } |
|
| 1100 |
+} |
|
| 1101 |
+ |
|
| 1086 | 1102 |
cl_error_t pre_hash_callback(cl_scan_layer_t *layer, void *context) |
| 1087 | 1103 |
{
|
| 1088 | 1104 |
cl_error_t status; |
| ... | ... |
@@ -1206,6 +1222,23 @@ static void printBytes(uint64_t bytes) |
| 1206 | 1206 |
} |
| 1207 | 1207 |
} |
| 1208 | 1208 |
|
| 1209 |
+int file_props_callback(const char *j_propstr, int rc, void *context) |
|
| 1210 |
+{
|
|
| 1211 |
+ (void)context; // Unused in this example |
|
| 1212 |
+ |
|
| 1213 |
+ printf("\n⭐In FILE_PROPS callback⭐\n");
|
|
| 1214 |
+ |
|
| 1215 |
+ if (j_propstr) {
|
|
| 1216 |
+ printf("%s\n", j_propstr);
|
|
| 1217 |
+ } |
|
| 1218 |
+ |
|
| 1219 |
+ printf("Metadata JSON Return Code: %s (%d)\n", cl_error_t_to_string((cl_error_t)rc), rc);
|
|
| 1220 |
+ |
|
| 1221 |
+ // Pass through the return code so as not to alter the scan return code. |
|
| 1222 |
+ // A real application might want to handle this differently. |
|
| 1223 |
+ return rc; |
|
| 1224 |
+} |
|
| 1225 |
+ |
|
| 1209 | 1226 |
/* |
| 1210 | 1227 |
* Exit codes: |
| 1211 | 1228 |
* 0: clean |
| ... | ... |
@@ -1226,6 +1259,9 @@ int main(int argc, char **argv) |
| 1226 | 1226 |
const char *hash_hint = NULL; |
| 1227 | 1227 |
const char *hash_alg = NULL; |
| 1228 | 1228 |
const char *file_type_hint = NULL; |
| 1229 |
+ bool allmatch = true; |
|
| 1230 |
+ bool gen_json = false; |
|
| 1231 |
+ bool debug_mode = false; |
|
| 1229 | 1232 |
|
| 1230 | 1233 |
script_context_t *script_context = NULL; |
| 1231 | 1234 |
|
| ... | ... |
@@ -1248,16 +1284,18 @@ int main(int argc, char **argv) |
| 1248 | 1248 |
"Example: %s -d /path/to/clamav.db -f /path/to/file.txt\n" |
| 1249 | 1249 |
"\n" |
| 1250 | 1250 |
"Options:\n" |
| 1251 |
- "--help (-h) : Help message.\n" |
|
| 1252 |
- "--database (-d) : Path to the ClamAV database.\n" |
|
| 1253 |
- "--file (-f) : Path to the file to scan.\n" |
|
| 1254 |
- "--hash_hint : (optional) Hash of file to scan.\n" |
|
| 1255 |
- "--hash_alg : (optional) Hash algorithm of hash_hint.\n" |
|
| 1256 |
- " Will also change the hash algorithm reported at end of scan.\n" |
|
| 1257 |
- "--file_type_hint : (optional) File type hint for the file to scan.\n" |
|
| 1258 |
- "--script : (optional) Path for non-interactive test script.\n" |
|
| 1259 |
- " Script must be a new-line delimited list of integers from 1-to-5\n" |
|
| 1260 |
- " Corresponding to the interactive scan options.\n" |
|
| 1251 |
+ "--help (-h) : Help message.\n" |
|
| 1252 |
+ "--database (-d) FILE : Path to the ClamAV database.\n" |
|
| 1253 |
+ "--file (-f) FILE : Path to the file to scan.\n" |
|
| 1254 |
+ "--hash-hint HASH : (optional) Hash of file to scan.\n" |
|
| 1255 |
+ "--hash-alg ALGORITHM : (optional) Hash algorithm of hash-hint.\n" |
|
| 1256 |
+ " Will also change the hash algorithm reported at end of scan.\n" |
|
| 1257 |
+ "--file-type-hint CL_TYPE_* : (optional) File type hint for the file to scan.\n" |
|
| 1258 |
+ "--script FILE : (optional) Path for non-interactive test script.\n" |
|
| 1259 |
+ " Script must be a new-line delimited list of integers from 1-to-5\n" |
|
| 1260 |
+ " Corresponding to the interactive scan options.\n" |
|
| 1261 |
+ "--one-match (-1) : Disable allmatch (stops scans after one match).\n" |
|
| 1262 |
+ "--gen-json : Generate scan metadata JSON.\n" |
|
| 1261 | 1263 |
"\n" |
| 1262 | 1264 |
"Scripted scan options are:\n" |
| 1263 | 1265 |
"%s"; |
| ... | ... |
@@ -1276,15 +1314,24 @@ int main(int argc, char **argv) |
| 1276 | 1276 |
} else if (strcmp(argv[i], "--script") == 0) {
|
| 1277 | 1277 |
script_filepath = argv[++i]; |
| 1278 | 1278 |
printf("Script file: %s\n", script_filepath);
|
| 1279 |
- } else if (strcmp(argv[i], "--hash_hint") == 0) {
|
|
| 1279 |
+ } else if (strcmp(argv[i], "--hash-hint") == 0) {
|
|
| 1280 | 1280 |
hash_hint = argv[++i]; |
| 1281 | 1281 |
printf("Hash hint: %s\n", hash_hint);
|
| 1282 |
- } else if (strcmp(argv[i], "--hash_alg") == 0) {
|
|
| 1282 |
+ } else if (strcmp(argv[i], "--hash-alg") == 0) {
|
|
| 1283 | 1283 |
hash_alg = argv[++i]; |
| 1284 | 1284 |
printf("Hash algorithm: %s\n", hash_alg);
|
| 1285 |
- } else if (strcmp(argv[i], "--file_type_hint") == 0) {
|
|
| 1285 |
+ } else if (strcmp(argv[i], "--file-type-hint") == 0) {
|
|
| 1286 | 1286 |
file_type_hint = argv[++i]; |
| 1287 | 1287 |
printf("File type hint: %s\n", file_type_hint);
|
| 1288 |
+ } else if (strcmp(argv[i], "--one-match") == 0 || strcmp(argv[i], "-1") == 0) {
|
|
| 1289 |
+ allmatch = false; |
|
| 1290 |
+ printf("Disabling allmatch (stops scans after one match).\n");
|
|
| 1291 |
+ } else if (strcmp(argv[i], "--gen-json") == 0) {
|
|
| 1292 |
+ gen_json = true; |
|
| 1293 |
+ printf("Enabling scan metadata JSON feature.\n");
|
|
| 1294 |
+ } else if (strcmp(argv[i], "--debug") == 0) {
|
|
| 1295 |
+ debug_mode = true; |
|
| 1296 |
+ printf("Enabling debug mode.\n");
|
|
| 1288 | 1297 |
} else {
|
| 1289 | 1298 |
printf("Unknown option: %s\n", argv[i]);
|
| 1290 | 1299 |
printf(help_string, argv[0], argv[0], command_list); |
| ... | ... |
@@ -1321,6 +1368,10 @@ int main(int argc, char **argv) |
| 1321 | 1321 |
goto done; |
| 1322 | 1322 |
} |
| 1323 | 1323 |
|
| 1324 |
+ if (debug_mode) {
|
|
| 1325 |
+ cl_debug(); |
|
| 1326 |
+ } |
|
| 1327 |
+ |
|
| 1324 | 1328 |
if (!(engine = cl_engine_new())) {
|
| 1325 | 1329 |
printf("Can't create new engine\n");
|
| 1326 | 1330 |
goto done; |
| ... | ... |
@@ -1352,10 +1403,14 @@ int main(int argc, char **argv) |
| 1352 | 1352 |
|
| 1353 | 1353 |
/* Enable all parsers plus heuristics, allmatch, and the gen-json metadata feature. */ |
| 1354 | 1354 |
memset(&options, 0, sizeof(struct cl_scan_options)); |
| 1355 |
- options.parse |= ~0; /* enable all parsers */ |
|
| 1356 |
- options.general |= CL_SCAN_GENERAL_HEURISTICS; /* enable heuristic alert options */ |
|
| 1357 |
- options.general |= CL_SCAN_GENERAL_ALLMATCHES; /* run in all-match mode, so it keeps looking for alerts after the first one */ |
|
| 1358 |
- options.general |= CL_SCAN_GENERAL_COLLECT_METADATA; /* collect metadata may enable collecting additional filenames (like in zip) */ |
|
| 1355 |
+ options.parse |= ~0; /* enable all parsers */ |
|
| 1356 |
+ options.general |= CL_SCAN_GENERAL_HEURISTICS; /* enable heuristic alert options */ |
|
| 1357 |
+ if (allmatch) {
|
|
| 1358 |
+ options.general |= CL_SCAN_GENERAL_ALLMATCHES; /* run in all-match mode, so it keeps looking for alerts after the first one */ |
|
| 1359 |
+ } |
|
| 1360 |
+ if (gen_json) {
|
|
| 1361 |
+ options.general |= CL_SCAN_GENERAL_COLLECT_METADATA; /* collect metadata may enable collecting additional filenames (like in zip) */ |
|
| 1362 |
+ } |
|
| 1359 | 1363 |
|
| 1360 | 1364 |
/* |
| 1361 | 1365 |
* Set our callbacks. |
| ... | ... |
@@ -1365,11 +1420,12 @@ int main(int argc, char **argv) |
| 1365 | 1365 |
cl_engine_set_scan_callback(engine, &post_scan_callback, CL_SCAN_CALLBACK_POST_SCAN); |
| 1366 | 1366 |
cl_engine_set_scan_callback(engine, &alert_callback, CL_SCAN_CALLBACK_ALERT); |
| 1367 | 1367 |
cl_engine_set_scan_callback(engine, &file_type_callback, CL_SCAN_CALLBACK_FILE_TYPE); |
| 1368 |
+ if (gen_json) {
|
|
| 1369 |
+ cl_engine_set_clcb_file_props(engine, &file_props_callback); |
|
| 1370 |
+ } |
|
| 1368 | 1371 |
|
| 1369 | 1372 |
printf("Testing scan layer callbacks on: %s (fd: %d)\n", filename, target_fd);
|
| 1370 | 1373 |
|
| 1371 |
- // cl_debug(); |
|
| 1372 |
- |
|
| 1373 | 1374 |
/* |
| 1374 | 1375 |
* Run the scan. |
| 1375 | 1376 |
* Note that the callbacks will be called during this function. |
| ... | ... |
@@ -1405,22 +1461,9 @@ int main(int argc, char **argv) |
| 1405 | 1405 |
} else {
|
| 1406 | 1406 |
printf("No file type provided for this file.\n");
|
| 1407 | 1407 |
} |
| 1408 |
- switch (verdict) {
|
|
| 1409 |
- case CL_VERDICT_NOTHING_FOUND: {
|
|
| 1410 |
- printf("Verdict: Nothing found.\n");
|
|
| 1411 |
- } break; |
|
| 1412 |
- |
|
| 1413 |
- case CL_VERDICT_TRUSTED: {
|
|
| 1414 |
- printf("Verdict: Trusted.\n");
|
|
| 1415 |
- } break; |
|
| 1416 |
- |
|
| 1417 |
- case CL_VERDICT_STRONG_INDICATOR: {
|
|
| 1418 |
- printf("Verdict: Found Strong Indicator: %s\n", alert_name);
|
|
| 1419 |
- } break; |
|
| 1420 |
- |
|
| 1421 |
- case CL_VERDICT_POTENTIALLY_UNWANTED: {
|
|
| 1422 |
- printf("Verdict: Found Potentially Unwanted Indicator: %s\n", alert_name);
|
|
| 1423 |
- } break; |
|
| 1408 |
+ printf("Verdict: %s\n", cl_verdict_t_to_string(verdict));
|
|
| 1409 |
+ if (alert_name) {
|
|
| 1410 |
+ printf("Alert Name: %s\n", alert_name);
|
|
| 1424 | 1411 |
} |
| 1425 | 1412 |
printf("Return Code: %s (%d)\n", cl_error_t_to_string(ret), ret);
|
| 1426 | 1413 |
|
| ... | ... |
@@ -1123,9 +1123,9 @@ extern void cl_engine_set_clcb_pre_scan(struct cl_engine *engine, clcb_pre_scan |
| 1123 | 1123 |
* @param result The scan result for the file. |
| 1124 | 1124 |
* @param virname A signature name if there was one or more matches. |
| 1125 | 1125 |
* @param context Opaque application provided data. |
| 1126 |
- * @return Scan result is not overridden. |
|
| 1127 |
- * @return CL_BREAK = Allowed by callback - scan result is set to CL_CLEAN. |
|
| 1128 |
- * @return Blocked by callback - scan result is set to CL_VIRUS. |
|
| 1126 |
+ * @return CL_CLEAN = File is scanned. |
|
| 1127 |
+ * @return CL_BREAK = Allowed by callback - file is skipped and marked as clean. |
|
| 1128 |
+ * @return CL_VIRUS = Blocked by callback - file is skipped and marked as infected. |
|
| 1129 | 1129 |
*/ |
| 1130 | 1130 |
typedef cl_error_t (*clcb_post_scan)(int fd, int result, const char *virname, void *context); |
| 1131 | 1131 |
/** |
| ... | ... |
@@ -4302,6 +4302,7 @@ void emax_reached(cli_ctx *ctx) |
| 4302 | 4302 |
static cl_error_t dispatch_file_inspection_callback(clcb_file_inspection cb, cli_ctx *ctx, const char *filetype) |
| 4303 | 4303 |
{
|
| 4304 | 4304 |
cl_error_t status = CL_CLEAN; |
| 4305 |
+ cl_error_t append_ret; |
|
| 4305 | 4306 |
|
| 4306 | 4307 |
int fd = -1; |
| 4307 | 4308 |
uint32_t fmap_index = ctx->recursion_level; /* index of current file */ |
| ... | ... |
@@ -4347,18 +4348,24 @@ static cl_error_t dispatch_file_inspection_callback(clcb_file_inspection cb, cli |
| 4347 | 4347 |
|
| 4348 | 4348 |
switch (status) {
|
| 4349 | 4349 |
case CL_BREAK: |
| 4350 |
- cli_dbgmsg("dispatch_file_inspection_callback: scan cancelled by callback\n");
|
|
| 4351 |
- status = CL_BREAK; |
|
| 4350 |
+ cli_dbgmsg("dispatch_file_inspection_callback: file trusted by callback\n");
|
|
| 4351 |
+ |
|
| 4352 |
+ // Remove any evidence for this layer and set the verdict to trusted. |
|
| 4353 |
+ (void)cli_trust_this_layer(ctx); |
|
| 4354 |
+ |
|
| 4352 | 4355 |
break; |
| 4353 | 4356 |
case CL_VIRUS: |
| 4354 | 4357 |
cli_dbgmsg("dispatch_file_inspection_callback: file blocked by callback\n");
|
| 4355 |
- cli_append_virus(ctx, "Detected.By.Callback.Inspection"); |
|
| 4356 |
- status = CL_VIRUS; |
|
| 4358 |
+ append_ret = cli_append_virus(ctx, "Detected.By.Callback.Inspection"); |
|
| 4359 |
+ if (append_ret == CL_VIRUS) {
|
|
| 4360 |
+ status = CL_VIRUS; |
|
| 4361 |
+ } |
|
| 4357 | 4362 |
break; |
| 4358 |
- case CL_CLEAN: |
|
| 4363 |
+ case CL_SUCCESS: |
|
| 4364 |
+ // No action requested by callback. Keep scanning. |
|
| 4359 | 4365 |
break; |
| 4360 | 4366 |
default: |
| 4361 |
- status = CL_CLEAN; |
|
| 4367 |
+ status = CL_SUCCESS; |
|
| 4362 | 4368 |
cli_warnmsg("dispatch_file_inspection_callback: ignoring bad return code from callback\n");
|
| 4363 | 4369 |
} |
| 4364 | 4370 |
|
| ... | ... |
@@ -4371,6 +4378,7 @@ done: |
| 4371 | 4371 |
static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, const char *filetype) |
| 4372 | 4372 |
{
|
| 4373 | 4373 |
cl_error_t status = CL_CLEAN; |
| 4374 |
+ cl_error_t append_ret; |
|
| 4374 | 4375 |
|
| 4375 | 4376 |
if (cb) {
|
| 4376 | 4377 |
perf_start(ctx, PERFT_PRECB); |
| ... | ... |
@@ -4388,12 +4396,16 @@ static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, cons |
| 4388 | 4388 |
break; |
| 4389 | 4389 |
case CL_VIRUS: |
| 4390 | 4390 |
cli_dbgmsg("dispatch_prescan_callback: file blocked by callback\n");
|
| 4391 |
- status = cli_append_virus(ctx, "Detected.By.Callback"); |
|
| 4391 |
+ append_ret = cli_append_virus(ctx, "Detected.By.Callback"); |
|
| 4392 |
+ if (append_ret == CL_VIRUS) {
|
|
| 4393 |
+ status = CL_VIRUS; |
|
| 4394 |
+ } |
|
| 4392 | 4395 |
break; |
| 4393 |
- case CL_CLEAN: |
|
| 4396 |
+ case CL_SUCCESS: |
|
| 4397 |
+ // No action requested by callback. Keep scanning. |
|
| 4394 | 4398 |
break; |
| 4395 | 4399 |
default: |
| 4396 |
- status = CL_CLEAN; |
|
| 4400 |
+ status = CL_SUCCESS; |
|
| 4397 | 4401 |
cli_warnmsg("dispatch_prescan_callback: ignoring bad return code from callback\n");
|
| 4398 | 4402 |
} |
| 4399 | 4403 |
} |
| ... | ... |
@@ -4552,38 +4564,40 @@ done: |
| 4552 | 4552 |
|
| 4553 | 4553 |
cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4554 | 4554 |
{
|
| 4555 |
- cl_error_t ret = CL_CLEAN; |
|
| 4555 |
+ cl_error_t status = CL_SUCCESS; |
|
| 4556 |
+ cl_error_t ret; |
|
| 4557 |
+ |
|
| 4556 | 4558 |
cl_error_t cache_check_result = CL_VIRUS; |
| 4557 | 4559 |
cl_verdict_t verdict_at_this_level = CL_VERDICT_NOTHING_FOUND; |
| 4558 | 4560 |
|
| 4559 | 4561 |
bool cache_enabled = true; |
| 4560 |
- cli_file_t dettype = 0; |
|
| 4562 |
+ cli_file_t dettype = CL_TYPE_ANY; |
|
| 4561 | 4563 |
uint8_t typercg = 1; |
| 4562 | 4564 |
bitset_t *old_hook_lsig_matches = NULL; |
| 4563 | 4565 |
const char *filetype; |
| 4564 | 4566 |
|
| 4565 | 4567 |
if (!ctx->engine) {
|
| 4566 | 4568 |
cli_errmsg("CRITICAL: engine == NULL\n");
|
| 4567 |
- ret = CL_ENULLARG; |
|
| 4569 |
+ status = CL_ENULLARG; |
|
| 4568 | 4570 |
goto early_ret; |
| 4569 | 4571 |
} |
| 4570 | 4572 |
|
| 4571 | 4573 |
if (!(ctx->engine->dboptions & CL_DB_COMPILED)) {
|
| 4572 | 4574 |
cli_errmsg("CRITICAL: engine not compiled\n");
|
| 4573 |
- ret = CL_EMALFDB; |
|
| 4575 |
+ status = CL_EMALFDB; |
|
| 4574 | 4576 |
goto early_ret; |
| 4575 | 4577 |
} |
| 4576 | 4578 |
|
| 4577 | 4579 |
if (ctx->fmap->len <= 5) {
|
| 4578 |
- ret = CL_CLEAN; |
|
| 4580 |
+ status = CL_SUCCESS; |
|
| 4579 | 4581 |
cli_dbgmsg("cli_magic_scan: File is too small (%zu bytes), ignoring.\n", ctx->fmap->len);
|
| 4580 | 4582 |
goto early_ret; |
| 4581 | 4583 |
} |
| 4582 | 4584 |
|
| 4583 |
- if (cli_updatelimits(ctx, ctx->fmap->len) != CL_CLEAN) {
|
|
| 4585 |
+ if (cli_updatelimits(ctx, ctx->fmap->len) != CL_SUCCESS) {
|
|
| 4584 | 4586 |
emax_reached(ctx); |
| 4585 |
- ret = CL_CLEAN; |
|
| 4586 |
- cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__);
|
|
| 4587 |
+ status = CL_SUCCESS; |
|
| 4588 |
+ cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", status, __AT__);
|
|
| 4587 | 4589 |
goto early_ret; |
| 4588 | 4590 |
} |
| 4589 | 4591 |
|
| ... | ... |
@@ -4604,9 +4618,9 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4604 | 4604 |
} |
| 4605 | 4605 |
perf_stop(ctx, PERFT_FT); |
| 4606 | 4606 |
if (type == CL_TYPE_ERROR) {
|
| 4607 |
- ret = CL_EREAD; |
|
| 4607 |
+ status = CL_EREAD; |
|
| 4608 | 4608 |
cli_dbgmsg("cli_magic_scan: cli_determine_fmap_type returned CL_TYPE_ERROR\n");
|
| 4609 |
- cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__);
|
|
| 4609 |
+ cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", status, __AT__);
|
|
| 4610 | 4610 |
goto early_ret; |
| 4611 | 4611 |
} |
| 4612 | 4612 |
filetype = cli_ftname(type); |
| ... | ... |
@@ -4615,8 +4629,9 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4615 | 4615 |
ret = cli_recursion_stack_change_type(ctx, type, true /* ? */); |
| 4616 | 4616 |
if (CL_SUCCESS != ret) {
|
| 4617 | 4617 |
cli_dbgmsg("cli_magic_scan: cli_recursion_stack_change_type returned %d\n", ret);
|
| 4618 |
- cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__);
|
|
| 4619 |
- goto early_ret; |
|
| 4618 |
+ // We must go to done here (and not early_ret), because `ret` needs to be tidied up before returning. |
|
| 4619 |
+ status = ret; |
|
| 4620 |
+ goto done; |
|
| 4620 | 4621 |
} |
| 4621 | 4622 |
|
| 4622 | 4623 |
/* |
| ... | ... |
@@ -4624,6 +4639,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4624 | 4624 |
*/ |
| 4625 | 4625 |
ret = cli_dispatch_scan_callback(ctx, CL_SCAN_CALLBACK_PRE_HASH); |
| 4626 | 4626 |
if (CL_SUCCESS != ret) {
|
| 4627 |
+ status = ret; |
|
| 4627 | 4628 |
goto done; |
| 4628 | 4629 |
} |
| 4629 | 4630 |
|
| ... | ... |
@@ -4632,6 +4648,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4632 | 4632 |
*/ |
| 4633 | 4633 |
ret = dispatch_prescan_callback(ctx->engine->cb_pre_cache, ctx, filetype); |
| 4634 | 4634 |
if (CL_VERIFIED == ret || CL_VIRUS == ret) {
|
| 4635 |
+ status = ret; |
|
| 4635 | 4636 |
goto done; |
| 4636 | 4637 |
} |
| 4637 | 4638 |
|
| ... | ... |
@@ -4645,6 +4662,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4645 | 4645 |
} else {
|
| 4646 | 4646 |
ret = CL_CLEAN; |
| 4647 | 4647 |
} |
| 4648 |
+ status = ret; |
|
| 4648 | 4649 |
goto done; |
| 4649 | 4650 |
} |
| 4650 | 4651 |
|
| ... | ... |
@@ -4670,6 +4688,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4670 | 4670 |
ret = fmap_will_need_hash_later(ctx->fmap, hash_type); |
| 4671 | 4671 |
if (CL_SUCCESS != ret) {
|
| 4672 | 4672 |
cli_dbgmsg("cli_check_fp: Failed to set fmap to need the %s hash later\n", cli_hash_name(hash_type));
|
| 4673 |
+ status = ret; |
|
| 4673 | 4674 |
goto done; |
| 4674 | 4675 |
} |
| 4675 | 4676 |
} |
| ... | ... |
@@ -4686,7 +4705,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4686 | 4686 |
cli_dbgmsg("cli_magic_scan: Failed to get a hash for the current fmap.\n");
|
| 4687 | 4687 |
// It may be that the file was truncated between the time we started the scan and the time we got the hash. |
| 4688 | 4688 |
// Not a reason to print an error message. |
| 4689 |
- ret = CL_SUCCESS; |
|
| 4689 |
+ status = CL_SUCCESS; |
|
| 4690 | 4690 |
goto done; |
| 4691 | 4691 |
} |
| 4692 | 4692 |
|
| ... | ... |
@@ -4698,8 +4717,9 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4698 | 4698 |
|
| 4699 | 4699 |
ret = cli_jsonstr(ctx->this_layer_metadata_json, cli_hash_name(hash_type), hash_string); |
| 4700 | 4700 |
if (ret != CL_SUCCESS) {
|
| 4701 |
- cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__);
|
|
| 4702 |
- goto early_ret; |
|
| 4701 |
+ cli_dbgmsg("cli_magic_scan: Failed to store the %s hash in the metadata JSON.\n", cli_hash_name(hash_type));
|
|
| 4702 |
+ status = ret; |
|
| 4703 |
+ goto done; |
|
| 4703 | 4704 |
} |
| 4704 | 4705 |
} |
| 4705 | 4706 |
} |
| ... | ... |
@@ -4715,8 +4735,10 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4715 | 4715 |
} |
| 4716 | 4716 |
|
| 4717 | 4717 |
if (cache_enabled && (cache_check_result != CL_VIRUS)) {
|
| 4718 |
- ret = CL_SUCCESS; |
|
| 4719 |
- cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", ret, __AT__);
|
|
| 4718 |
+ status = CL_SUCCESS; |
|
| 4719 |
+ cli_dbgmsg("cli_magic_scan: returning %d %s (no post, no cache)\n", status, __AT__);
|
|
| 4720 |
+ // We can go to early_ret here, because we know status is CL_SUCCESS, and we obviously add to the cache. |
|
| 4721 |
+ // This does mean, however, that we do not run the post-scan callback for layers that are cached. |
|
| 4720 | 4722 |
goto early_ret; |
| 4721 | 4723 |
} |
| 4722 | 4724 |
|
| ... | ... |
@@ -4729,6 +4751,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4729 | 4729 |
*/ |
| 4730 | 4730 |
ret = cli_dispatch_scan_callback(ctx, CL_SCAN_CALLBACK_PRE_SCAN); |
| 4731 | 4731 |
if (CL_SUCCESS != ret) {
|
| 4732 |
+ status = ret; |
|
| 4732 | 4733 |
goto done; |
| 4733 | 4734 |
} |
| 4734 | 4735 |
|
| ... | ... |
@@ -4737,13 +4760,14 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4737 | 4737 |
*/ |
| 4738 | 4738 |
ret = dispatch_prescan_callback(ctx->engine->cb_pre_scan, ctx, filetype); |
| 4739 | 4739 |
if (CL_VERIFIED == ret || CL_VIRUS == ret) {
|
| 4740 |
+ status = ret; |
|
| 4740 | 4741 |
goto done; |
| 4741 | 4742 |
} |
| 4742 | 4743 |
|
| 4743 | 4744 |
// If none of the scan options are enabled, then we can skip parsing and just do a raw pattern match. |
| 4744 | 4745 |
// For this check, we don't care if the CL_SCAN_GENERAL_ALLMATCHES option is enabled, hence the `~`. |
| 4745 | 4746 |
if (!((ctx->options->general & ~CL_SCAN_GENERAL_ALLMATCHES) || (ctx->options->parse) || (ctx->options->heuristic) || (ctx->options->mail) || (ctx->options->dev))) {
|
| 4746 |
- ret = cli_scan_fmap(ctx, CL_TYPE_ANY, false, NULL, AC_SCAN_VIR, NULL); |
|
| 4747 |
+ status = cli_scan_fmap(ctx, CL_TYPE_ANY, false, NULL, AC_SCAN_VIR, NULL); |
|
| 4747 | 4748 |
// It doesn't matter what was returned, always go to the end after this. Raw mode! No parsing files! |
| 4748 | 4749 |
goto done; |
| 4749 | 4750 |
} |
| ... | ... |
@@ -4752,7 +4776,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4752 | 4752 |
// The ctx one is NULL at present. |
| 4753 | 4753 |
ctx->hook_lsig_matches = cli_bitset_init(); |
| 4754 | 4754 |
if (NULL == ctx->hook_lsig_matches) {
|
| 4755 |
- ret = CL_EMEM; |
|
| 4755 |
+ status = CL_EMEM; |
|
| 4756 | 4756 |
goto done; |
| 4757 | 4757 |
} |
| 4758 | 4758 |
|
| ... | ... |
@@ -4765,7 +4789,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 4765 | 4765 |
|
| 4766 | 4766 |
// Evaluate the result from the scan to see if it end the scan of this layer early, |
| 4767 | 4767 |
// and to decid if we should propagate an error or not. |
| 4768 |
- if (result_should_goto_done(ctx, ret, &ret)) {
|
|
| 4768 |
+ if (result_should_goto_done(ctx, ret, &status)) {
|
|
| 4769 | 4769 |
goto done; |
| 4770 | 4770 |
} |
| 4771 | 4771 |
} |
| ... | ... |
@@ -5196,7 +5220,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 5196 | 5196 |
|
| 5197 | 5197 |
// Evaluate the result from the parsers to see if it end the scan of this layer early, |
| 5198 | 5198 |
// and to decide if we should propagate an error or not. |
| 5199 |
- if (result_should_goto_done(ctx, ret, &ret)) {
|
|
| 5199 |
+ if (result_should_goto_done(ctx, ret, &status)) {
|
|
| 5200 | 5200 |
goto done; |
| 5201 | 5201 |
} |
| 5202 | 5202 |
|
| ... | ... |
@@ -5228,7 +5252,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type) |
| 5228 | 5228 |
|
| 5229 | 5229 |
// Evaluate the result from the scan to see if it end the scan of this layer early, |
| 5230 | 5230 |
// and to decid if we should propagate an error or not. |
| 5231 |
- if (result_should_goto_done(ctx, ret, &ret)) {
|
|
| 5231 |
+ if (result_should_goto_done(ctx, ret, &status)) {
|
|
| 5232 | 5232 |
goto done; |
| 5233 | 5233 |
} |
| 5234 | 5234 |
} |
| ... | ... |
@@ -5350,17 +5374,22 @@ done: |
| 5350 | 5350 |
* Run the post_scan callback. |
| 5351 | 5351 |
*/ |
| 5352 | 5352 |
ret = cli_dispatch_scan_callback(ctx, CL_SCAN_CALLBACK_POST_SCAN); |
| 5353 |
+ if (CL_SUCCESS != ret) {
|
|
| 5354 |
+ cli_dbgmsg("cli_magic_scan: POST_SCAN callback returned %d\n", ret);
|
|
| 5355 |
+ status = ret; |
|
| 5356 |
+ } |
|
| 5353 | 5357 |
|
| 5354 | 5358 |
// Filter the result from the parsers so we don't propagate non-fatal errors. |
| 5355 |
- // And to convert CL_VERIFIED -> CL_CLEAN |
|
| 5356 |
- (void)result_should_goto_done(ctx, ret, &ret); |
|
| 5359 |
+ // And to convert CL_VERIFIED -> CL_SUCCESS |
|
| 5360 |
+ (void)result_should_goto_done(ctx, status, &status); |
|
| 5357 | 5361 |
|
| 5358 | 5362 |
/* |
| 5359 | 5363 |
* Run the deprecated post-scan callback (if one exists) and provide the verdict for this layer. |
| 5360 | 5364 |
*/ |
| 5361 |
- cli_dbgmsg("cli_magic_scan: returning %d %s\n", ret, __AT__);
|
|
| 5365 |
+ cli_dbgmsg("cli_magic_scan: returning %d %s\n", status, __AT__);
|
|
| 5362 | 5366 |
if (ctx->engine->cb_post_scan) {
|
| 5363 | 5367 |
cl_error_t callback_ret; |
| 5368 |
+ cl_error_t append_ret; |
|
| 5364 | 5369 |
const char *virusname = NULL; |
| 5365 | 5370 |
|
| 5366 | 5371 |
// Get the last signature that matched (if any). |
| ... | ... |
@@ -5375,23 +5404,34 @@ done: |
| 5375 | 5375 |
switch (callback_ret) {
|
| 5376 | 5376 |
case CL_BREAK: |
| 5377 | 5377 |
cli_dbgmsg("cli_magic_scan: file allowed by post_scan callback\n");
|
| 5378 |
- ret = CL_CLEAN; |
|
| 5378 |
+ |
|
| 5379 |
+ // Remove any evidence for this layer and set the verdict to trusted. |
|
| 5380 |
+ (void)cli_trust_this_layer(ctx); |
|
| 5381 |
+ |
|
| 5382 |
+ //status = CL_SUCCESS; // Do override the status here. |
|
| 5383 |
+ // If status == CL_VIRUS, we'll fix when we look at the verdict. |
|
| 5379 | 5384 |
break; |
| 5380 | 5385 |
case CL_VIRUS: |
| 5381 | 5386 |
cli_dbgmsg("cli_magic_scan: file blocked by post_scan callback\n");
|
| 5382 |
- callback_ret = cli_append_virus(ctx, "Detected.By.Callback"); |
|
| 5383 |
- if (callback_ret == CL_VIRUS) {
|
|
| 5384 |
- ret = CL_VIRUS; |
|
| 5387 |
+ append_ret = cli_append_virus(ctx, "Detected.By.Callback"); |
|
| 5388 |
+ if (append_ret == CL_VIRUS) {
|
|
| 5389 |
+ status = CL_VIRUS; |
|
| 5385 | 5390 |
} |
| 5386 | 5391 |
break; |
| 5387 |
- case CL_CLEAN: |
|
| 5392 |
+ case CL_SUCCESS: |
|
| 5393 |
+ // No action requested by callback. Keep scanning. |
|
| 5388 | 5394 |
break; |
| 5389 | 5395 |
default: |
| 5390 |
- ret = CL_CLEAN; |
|
| 5396 |
+ //status = CL_SUCCESS; // Do override the status here, just log a warning. |
|
| 5391 | 5397 |
cli_warnmsg("cli_magic_scan: ignoring bad return code from post_scan callback\n");
|
| 5392 | 5398 |
} |
| 5393 | 5399 |
} |
| 5394 | 5400 |
|
| 5401 |
+ /* |
|
| 5402 |
+ * Check the verdict for this layer. |
|
| 5403 |
+ * If the verdict is CL_VERDICT_TRUSTED, remove any evidence for this layer and clear CL_VIRUS status (if set) |
|
| 5404 |
+ * Otherwise, we'll update the verdict based on the evidence. |
|
| 5405 |
+ */ |
|
| 5395 | 5406 |
if (CL_VERDICT_TRUSTED == ctx->recursion_stack[ctx->recursion_level].verdict) {
|
| 5396 | 5407 |
/* Remove any alerts for this layer. */ |
| 5397 | 5408 |
if (NULL != ctx->recursion_stack[ctx->recursion_level].evidence) {
|
| ... | ... |
@@ -5399,6 +5439,9 @@ done: |
| 5399 | 5399 |
ctx->recursion_stack[ctx->recursion_level].evidence = NULL; |
| 5400 | 5400 |
ctx->this_layer_evidence = NULL; |
| 5401 | 5401 |
} |
| 5402 |
+ if (CL_VIRUS == status) {
|
|
| 5403 |
+ status = CL_SUCCESS; // If we have a CL_VERDICT_TRUSTED, we should not return CL_VIRUS. |
|
| 5404 |
+ } |
|
| 5402 | 5405 |
} else {
|
| 5403 | 5406 |
/* |
| 5404 | 5407 |
* Update the verdict for this layer based on the scan results. |
| ... | ... |
@@ -5437,7 +5480,7 @@ early_ret: |
| 5437 | 5437 |
ctx->hook_lsig_matches = old_hook_lsig_matches; |
| 5438 | 5438 |
} |
| 5439 | 5439 |
|
| 5440 |
- return ret; |
|
| 5440 |
+ return status; |
|
| 5441 | 5441 |
} |
| 5442 | 5442 |
|
| 5443 | 5443 |
cl_error_t cli_magic_scan_desc_type(int desc, const char *filepath, cli_ctx *ctx, cli_file_t type, |
| ... | ... |
@@ -9,16 +9,18 @@ For reference: |
| 9 | 9 |
Example: ./install/bin/ex_scan_callbacks -d /path/to/clamav.db -f /path/to/file.txt |
| 10 | 10 |
|
| 11 | 11 |
Options: |
| 12 |
- --help (-h) : Help message. |
|
| 13 |
- --database (-d) : Path to the ClamAV database. |
|
| 14 |
- --file (-f) : Path to the file to scan. |
|
| 15 |
- --hash_hint : (optional) Hash of file to scan. |
|
| 16 |
- --hash_alg : (optional) Hash algorithm of hash_hint. |
|
| 17 |
- Will also change the hash algorithm reported at end of scan. |
|
| 18 |
- --file_type_hint : (optional) File type hint for the file to scan. |
|
| 19 |
- --script : (optional) Path for non-interactive test script. |
|
| 20 |
- Script must be a new-line delimited list of integers from 1-to-5 |
|
| 21 |
- Corresponding to the interactive scan options. |
|
| 12 |
+ --help (-h) : Help message. |
|
| 13 |
+ --database (-d) FILE : Path to the ClamAV database. |
|
| 14 |
+ --file (-f) FILE : Path to the file to scan. |
|
| 15 |
+ --hash-hint HASH : (optional) Hash of file to scan. |
|
| 16 |
+ --hash-alg ALGORITHM : (optional) Hash algorithm of hash-hint. |
|
| 17 |
+ Will also change the hash algorithm reported at end of scan. |
|
| 18 |
+ --file-type-hint CL_TYPE_* : (optional) File type hint for the file to scan. |
|
| 19 |
+ --script FILE : (optional) Path for non-interactive test script. |
|
| 20 |
+ Script must be a new-line delimited list of integers from 1-to-5 |
|
| 21 |
+ Corresponding to the interactive scan options. |
|
| 22 |
+ --one-match (-1) : Disable allmatch (stops scans after one match). |
|
| 23 |
+ --gen-json : Generate scan metadata JSON. |
|
| 22 | 24 |
|
| 23 | 25 |
Scripted scan options are: |
| 24 | 26 |
1 - Return CL_BREAK to abort scanning. Will still encounter POST_SCAN-callbacks on the way out. |
| ... | ... |
@@ -32,6 +34,7 @@ For reference: |
| 32 | 32 |
9 - Get sha1 hash. Does not return from the callback! |
| 33 | 33 |
10 - Get sha2-256 hash. Does not return from the callback! |
| 34 | 34 |
11 - Print all hashes that have already been calculated. Does not return from the callback! |
| 35 |
+ |
|
| 35 | 36 |
""" |
| 36 | 37 |
|
| 37 | 38 |
import os |
| ... | ... |
@@ -176,7 +179,7 @@ class TC(testcase.TestCase): |
| 176 | 176 |
'Data scanned: 948 B', |
| 177 | 177 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 178 | 178 |
'File Type: CL_TYPE_ZIP', |
| 179 |
- 'Verdict: Found Strong Indicator', |
|
| 179 |
+ 'Verdict: CL_VERDICT_STRONG_INDICATOR', |
|
| 180 | 180 |
'Return Code: CL_SUCCESS (0)', |
| 181 | 181 |
] |
| 182 | 182 |
|
| ... | ... |
@@ -188,7 +191,7 @@ class TC(testcase.TestCase): |
| 188 | 188 |
) |
| 189 | 189 |
output = self.execute_command(command) |
| 190 | 190 |
|
| 191 |
- # Check for success |
|
| 191 |
+ # Check for CL_SUCCESS return code |
|
| 192 | 192 |
assert output.ec == 0 |
| 193 | 193 |
|
| 194 | 194 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| ... | ... |
@@ -204,6 +207,125 @@ class TC(testcase.TestCase): |
| 204 | 204 |
|
| 205 | 205 |
remaining_output = parts[1] |
| 206 | 206 |
|
| 207 |
+ def test_cl_scan_callbacks_clam_zip_basic_one_match(self): |
|
| 208 |
+ self.step_name('Same as basic test with clam.zip that just keeps scanning--but disables allmatch mode.')
|
|
| 209 |
+ |
|
| 210 |
+ # Notably, the return code at the end should be CL_VIRUS (1) instead of CL_SUCCESS (0). |
|
| 211 |
+ # This is because the reason the scan ended "early" is because of the alert in the clam.exe file. |
|
| 212 |
+ |
|
| 213 |
+ path_db = TC.path_source / 'unit_tests' / 'input' / 'clamav.hdb' |
|
| 214 |
+ |
|
| 215 |
+ # Build up expected results as we define the test script. |
|
| 216 |
+ expected_results = [] |
|
| 217 |
+ |
|
| 218 |
+ test_script = TC.path_tmp / 'zip_basic.txt' |
|
| 219 |
+ with open(test_script, 'w') as f: |
|
| 220 |
+ expected_results += [ |
|
| 221 |
+ 'In FILE_TYPE callback', |
|
| 222 |
+ 'Recursion Level: 0', |
|
| 223 |
+ 'File Name: clam.zip', |
|
| 224 |
+ 'File Type: CL_TYPE_ZIP', |
|
| 225 |
+ ] |
|
| 226 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 227 |
+ |
|
| 228 |
+ expected_results += [ |
|
| 229 |
+ 'In PRE_HASH callback', |
|
| 230 |
+ 'Recursion Level: 0', |
|
| 231 |
+ 'File Name: clam.zip', |
|
| 232 |
+ 'File Type: CL_TYPE_ZIP', |
|
| 233 |
+ ] |
|
| 234 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 235 |
+ |
|
| 236 |
+ expected_results += [ |
|
| 237 |
+ 'In PRE_SCAN callback', |
|
| 238 |
+ 'Recursion Level: 0', |
|
| 239 |
+ 'File Name: clam.zip', |
|
| 240 |
+ 'File Type: CL_TYPE_ZIP', |
|
| 241 |
+ ] |
|
| 242 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 243 |
+ |
|
| 244 |
+ expected_results += [ |
|
| 245 |
+ 'In FILE_TYPE callback', |
|
| 246 |
+ 'Recursion Level: 1', |
|
| 247 |
+ 'File Name: clam.exe', |
|
| 248 |
+ 'File Type: CL_TYPE_MSEXE', |
|
| 249 |
+ ] |
|
| 250 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 251 |
+ |
|
| 252 |
+ expected_results += [ |
|
| 253 |
+ 'In PRE_HASH callback', |
|
| 254 |
+ 'Recursion Level: 1', |
|
| 255 |
+ 'File Name: clam.exe', |
|
| 256 |
+ 'File Type: CL_TYPE_MSEXE', |
|
| 257 |
+ ] |
|
| 258 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 259 |
+ |
|
| 260 |
+ expected_results += [ |
|
| 261 |
+ 'In PRE_SCAN callback', |
|
| 262 |
+ 'Recursion Level: 1', |
|
| 263 |
+ 'File Name: clam.exe', |
|
| 264 |
+ 'File Type: CL_TYPE_MSEXE', |
|
| 265 |
+ ] |
|
| 266 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 267 |
+ |
|
| 268 |
+ expected_results += [ |
|
| 269 |
+ 'In ALERT callback', |
|
| 270 |
+ 'Recursion Level: 1', |
|
| 271 |
+ 'File Name: clam.exe', |
|
| 272 |
+ 'File Type: CL_TYPE_MSEXE', |
|
| 273 |
+ 'Last Alert: ClamAV-Test-File.UNOFFICIAL', |
|
| 274 |
+ ] |
|
| 275 |
+ f.write('3\n') # Return CL_VIRUS to keep scanning and accept the alert
|
|
| 276 |
+ |
|
| 277 |
+ expected_results += [ |
|
| 278 |
+ 'In POST_SCAN callback', |
|
| 279 |
+ 'Recursion Level: 1', |
|
| 280 |
+ 'File Name: clam.exe', |
|
| 281 |
+ 'File Type: CL_TYPE_MSEXE', |
|
| 282 |
+ 'Last Alert: ClamAV-Test-File.UNOFFICIAL', |
|
| 283 |
+ ] |
|
| 284 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 285 |
+ |
|
| 286 |
+ expected_results += [ |
|
| 287 |
+ 'Recursion Level: 0', |
|
| 288 |
+ 'In POST_SCAN callback', |
|
| 289 |
+ 'File Name: clam.zip', |
|
| 290 |
+ 'File Type: CL_TYPE_ZIP' |
|
| 291 |
+ ] |
|
| 292 |
+ f.write('2\n') # Return CL_SUCCESS to keep scanning
|
|
| 293 |
+ |
|
| 294 |
+ expected_results += [ |
|
| 295 |
+ 'Data scanned: 544 B', # Note this is less, because allmatch disabled so stopped after clam.exe matched. |
|
| 296 |
+ 'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
|
| 297 |
+ 'File Type: CL_TYPE_ZIP', |
|
| 298 |
+ 'Verdict: CL_VERDICT_STRONG_INDICATOR', |
|
| 299 |
+ 'Return Code: CL_VIRUS (1)', |
|
| 300 |
+ ] |
|
| 301 |
+ |
|
| 302 |
+ command = '{valgrind} {valgrind_args} {example} -d {database} -f {target} --script {script} --one-match'.format(
|
|
| 303 |
+ valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, example=TC.example_program, |
|
| 304 |
+ database=path_db, |
|
| 305 |
+ target=TC.path_build / 'unit_tests' / 'input' / 'clamav_hdb_scanfiles' / 'clam.zip', |
|
| 306 |
+ script=test_script |
|
| 307 |
+ ) |
|
| 308 |
+ output = self.execute_command(command) |
|
| 309 |
+ |
|
| 310 |
+ # Check for CL_VIRUS return code |
|
| 311 |
+ assert output.ec == 1 |
|
| 312 |
+ |
|
| 313 |
+ # Custom logic to verify the output making sure that all expected results are found in the output in order. |
|
| 314 |
+ # |
|
| 315 |
+ # This is necessary because the STRICT_ORDER option gets confused when expected results have multiple of the |
|
| 316 |
+ # same string, but in different contexts. |
|
| 317 |
+ remaining_output = output.out |
|
| 318 |
+ |
|
| 319 |
+ for expected in expected_results: |
|
| 320 |
+ # find the first occurrence of the expected string in remaining_output, splitting into two parts |
|
| 321 |
+ parts = remaining_output.split(expected, 1) |
|
| 322 |
+ assert len(parts) == 2, f"Expected '{expected}' in output, but it was not found:\n{remaining_output}"
|
|
| 323 |
+ |
|
| 324 |
+ remaining_output = parts[1] |
|
| 325 |
+ |
|
| 207 | 326 |
def test_cl_scan_callbacks_clam_zip_ignore_alert(self): |
| 208 | 327 |
self.step_name('Ignore alert in clam.exe (within clam.zip) and keep scanning.')
|
| 209 | 328 |
|
| ... | ... |
@@ -301,7 +423,7 @@ class TC(testcase.TestCase): |
| 301 | 301 |
) |
| 302 | 302 |
output = self.execute_command(command) |
| 303 | 303 |
|
| 304 |
- # Check for success |
|
| 304 |
+ # Check for CL_SUCCESS return code |
|
| 305 | 305 |
assert output.ec == 0 |
| 306 | 306 |
|
| 307 | 307 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| ... | ... |
@@ -350,7 +472,7 @@ class TC(testcase.TestCase): |
| 350 | 350 |
) |
| 351 | 351 |
output = self.execute_command(command) |
| 352 | 352 |
|
| 353 |
- # Check for success |
|
| 353 |
+ # Check for CL_SUCCESS return code |
|
| 354 | 354 |
assert output.ec == 0 |
| 355 | 355 |
|
| 356 | 356 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| ... | ... |
@@ -402,7 +524,7 @@ class TC(testcase.TestCase): |
| 402 | 402 |
'Data scanned: 0 B', |
| 403 | 403 |
'Hash: 21495c3a579d537dc63b0df710f63e60a0bfbc74d1c2739a313dbd42dd31e1fa', |
| 404 | 404 |
'File Type: CL_TYPE_ZIP', |
| 405 |
- 'Verdict: Found Strong Indicator', |
|
| 405 |
+ 'Verdict: CL_VERDICT_STRONG_INDICATOR', |
|
| 406 | 406 |
'Return Code: CL_SUCCESS (0)', |
| 407 | 407 |
] |
| 408 | 408 |
|
| ... | ... |
@@ -414,7 +536,7 @@ class TC(testcase.TestCase): |
| 414 | 414 |
) |
| 415 | 415 |
output = self.execute_command(command) |
| 416 | 416 |
|
| 417 |
- # Check for success |
|
| 417 |
+ # Check for CL_SUCCESS return code |
|
| 418 | 418 |
assert output.ec == 0 |
| 419 | 419 |
|
| 420 | 420 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |
| ... | ... |
@@ -543,7 +665,7 @@ class TC(testcase.TestCase): |
| 543 | 543 |
) |
| 544 | 544 |
output = self.execute_command(command) |
| 545 | 545 |
|
| 546 |
- # Check for success |
|
| 546 |
+ # Check for CL_SUCCESS return code |
|
| 547 | 547 |
assert output.ec == 0 |
| 548 | 548 |
|
| 549 | 549 |
# Custom logic to verify the output making sure that all expected results are found in the output in order. |