Browse code

Metadata JSON: Simplify recording alerts and indicators

We presently record Alerts as an array of signature names.
Instead, it should be an object with properties of its own.

We should record alerting indicators and weak indicators in a single
"Indicators", likely with the same structure as the "Alerts" objects.

When an alerting indicator is ignored (e.g. ignored by callback or if
the file is trusted by an FP signature), we can remove it from the
"Alerts" array, and for the "Indicators" array, add a "Ignored" key with
a string value that explains why it was ignored.

This eliminates the need to track and propagate the additional
"WeakIndicators" and "IgnoredAlerts" arrays.

Valerie Snyder authored on 2025/08/11 09:01:55
Showing 8 changed files
... ...
@@ -2442,7 +2442,7 @@ cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset
2442 2442
     cli_dbgmsg("asn1_check_mscat: file with valid authenticode signature, trusted\n");
2443 2443
 
2444 2444
     // Remove any evidence for this layer and set the verdict to trusted.
2445
-    (void)cli_trust_this_layer(ctx);
2445
+    (void)cli_trust_this_layer(ctx, "authenticode digital signature verification");
2446 2446
 
2447 2447
     return CL_VERIFIED;
2448 2448
 }
... ...
@@ -133,7 +133,7 @@ cl_error_t hm_addhash_bin(struct cl_engine *engine, hash_purpose_t purpose, cons
133 133
     struct cli_sz_hash *szh;
134 134
     struct cli_htu32 *ht;
135 135
     cl_error_t ret;
136
-    struct cli_matcher *root;
136
+    struct cli_matcher *root = NULL;
137 137
 
138 138
     if (purpose == HASH_PURPOSE_PE_SECTION_DETECT) {
139 139
         root = engine->hm_mdb;
... ...
@@ -587,6 +587,9 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
587 587
 
588 588
     stack_index = (int32_t)ctx->recursion_level;
589 589
 
590
+    char *source = NULL;
591
+    size_t source_len;
592
+
590 593
     while (stack_index >= 0) {
591 594
         map = ctx->recursion_stack[stack_index].fmap;
592 595
 
... ...
@@ -669,8 +672,17 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
669 669
                 if (cli_hm_scan(hash, map->len, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
670 670
                     cli_dbgmsg("cli_check_fp: Found false positive detection for %s (fp sig: %s)\n", cli_hash_name(hash_type), virname);
671 671
 
672
+                    source_len = strlen(virname) + strlen("false positive signature match: ") + 1;
673
+                    source     = malloc(source_len);
674
+                    if (source) {
675
+                        snprintf(source, source_len, "false positive signature match: %s", virname);
676
+                    }
677
+
672 678
                     // Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers.
673
-                    (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level);
679
+                    (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level, source);
680
+
681
+                    free(source);
682
+                    source = NULL;
674 683
 
675 684
                     status = CL_VERIFIED;
676 685
                     goto done;
... ...
@@ -678,8 +690,17 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
678 678
                 if (cli_hm_scan_wild(hash, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
679 679
                     cli_dbgmsg("cli_check_fp: Found false positive detection for %s (fp sig: %s)\n", cli_hash_name(hash_type), virname);
680 680
 
681
+                    source_len = strlen(virname) + strlen("false positive signature match: ") + 1;
682
+                    source     = malloc(source_len);
683
+                    if (source) {
684
+                        snprintf(source, source_len, "false positive signature match: %s", virname);
685
+                    }
686
+
681 687
                     // Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers.
682
-                    (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level);
688
+                    (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level, source);
689
+
690
+                    free(source);
691
+                    source = NULL;
683 692
 
684 693
                     status = CL_VERIFIED;
685 694
                     goto done;
... ...
@@ -691,8 +712,17 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
691 691
                     if (cli_hm_scan(hash, 1, &virname, ctx->engine->hm_fp, hash_type) == CL_VIRUS) {
692 692
                         cli_dbgmsg("cli_check_fp: Found .CAB false positive detection for %s via catalog file\n", cli_hash_name(hash_type));
693 693
 
694
+                        source_len = strlen(virname) + strlen("false positive signature match: ") + 1;
695
+                        source     = malloc(source_len);
696
+                        if (source) {
697
+                            snprintf(source, source_len, "false positive signature match: %s", virname);
698
+                        }
699
+
694 700
                         // Remove any evidence and set the verdict to trusted for the layer where the FP hash matched, and for all contained layers.
695
-                        (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level);
701
+                        (void)cli_trust_layers(ctx, (uint32_t)stack_index, ctx->recursion_level, source);
702
+
703
+                        free(source);
704
+                        source = NULL;
696 705
 
697 706
                         status = CL_VERIFIED;
698 707
                         goto done;
... ...
@@ -706,6 +736,10 @@ cl_error_t cli_check_fp(cli_ctx *ctx, const char *vname)
706 706
 
707 707
 done:
708 708
 
709
+    if (NULL != source) {
710
+        free(source);
711
+    }
712
+
709 713
     return status;
710 714
 }
711 715
 
... ...
@@ -1236,7 +1236,7 @@ cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, uint64_t need1, uint64
1236 1236
     /* Enforce global scan-size limit, if limit enabled */
1237 1237
     if (needed && (ctx->engine->maxscansize != 0) && (ctx->engine->maxscansize - ctx->scansize < needed)) {
1238 1238
         /* The size needed is greater than the remaining scansize ... Skip this file. */
1239
-        cli_dbgmsg("%s: scansize exceeded (initial: %lu, consumed: %lu, needed: %lu)\n", who, ctx->engine->maxscansize, ctx->scansize, needed);
1239
+        cli_dbgmsg("%s: scansize exceeded (initial: " STDu64 ", consumed: " STDu64 ", needed: " STDu64 ")\n", who, ctx->engine->maxscansize, ctx->scansize, needed);
1240 1240
         ret = CL_EMAXSIZE;
1241 1241
         cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxScanSize");
1242 1242
         goto done;
... ...
@@ -1245,7 +1245,7 @@ cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, uint64_t need1, uint64
1245 1245
     /* Enforce per-file file-size limit, if limit enabled */
1246 1246
     if (needed && (ctx->engine->maxfilesize != 0) && (ctx->engine->maxfilesize < needed)) {
1247 1247
         /* The size needed is greater than that limit ... Skip this file. */
1248
-        cli_dbgmsg("%s: filesize exceeded (allowed: %lu, needed: %lu)\n", who, ctx->engine->maxfilesize, needed);
1248
+        cli_dbgmsg("%s: filesize exceeded (allowed: " STDu64 ", needed: " STDu64 ")\n", who, ctx->engine->maxfilesize, needed);
1249 1249
         ret = CL_EMAXSIZE;
1250 1250
         cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFileSize");
1251 1251
         goto done;
... ...
@@ -1466,24 +1466,68 @@ cl_error_t cli_virus_found_cb(cli_ctx *ctx, const char *virname, bool is_potenti
1466 1466
         }
1467 1467
 
1468 1468
         if (SCAN_COLLECT_METADATA && ctx->this_layer_metadata_json) {
1469
-            // Note alerts ignored by callback in metadata.
1470
-            json_object *arrobj, *virobj;
1471
-            if (!json_object_object_get_ex(ctx->this_layer_metadata_json, "IgnoredAlerts", &arrobj)) {
1472
-                arrobj = json_object_new_array();
1473
-                if (NULL == arrobj) {
1474
-                    cli_errmsg("cli_append_virus: no memory for json ignored alerts array\n");
1475
-                    status = CL_EMEM;
1469
+            // Remove the last alert from the "Alerts" array.
1470
+            json_object *alerts = NULL;
1471
+            if (json_object_object_get_ex(ctx->this_layer_metadata_json, "Alerts", &alerts)) {
1472
+                int json_ret = 0;
1473
+
1474
+                // Get the index of the last alert.
1475
+                size_t num_alerts = json_object_array_length(alerts);
1476
+                if (0 == num_alerts) {
1477
+                    cli_errmsg("Attempting to ignore an alerts, but alert not found in metadata Alerts array.\n");
1478
+                    status = CL_ERROR;
1476 1479
                     goto done;
1477 1480
                 }
1481
+
1482
+                // Remove the alert from the Alerts array.
1483
+                json_ret = json_object_array_del_idx(alerts, num_alerts - 1, 1);
1484
+                if (0 != json_ret) {
1485
+                    cli_errmsg("Failed to remove alert from metadata JSON.\n");
1486
+                    status = CL_ERROR;
1487
+                    goto done;
1488
+                }
1489
+
1490
+                // If there aren't any other alerts, we should also delete the "Alerts" array.
1491
+                if (num_alerts == 1) {
1492
+                    json_object_object_del(ctx->this_layer_metadata_json, "Alerts");
1493
+                }
1478 1494
             }
1479
-            virobj = json_object_new_string(virname);
1480
-            if (NULL == virobj) {
1481
-                cli_errmsg("cli_append_virus: no memory for json ignored alert name object\n");
1482
-                status = CL_EMEM;
1483
-                goto done;
1495
+
1496
+            // Add "Ignored" key to the last alert from the "Indicators" array.
1497
+            json_object *indicators = NULL;
1498
+            if (json_object_object_get_ex(ctx->this_layer_metadata_json, "Indicators", &indicators)) {
1499
+                int json_ret = 0;
1500
+
1501
+                // Get the index of the last indicator.
1502
+                size_t num_indicators = json_object_array_length(indicators);
1503
+                if (0 == num_indicators) {
1504
+                    cli_errmsg("Attempting to ignore an alerts, but alert not found in metadata Alerts array.\n");
1505
+                    status = CL_ERROR;
1506
+                    goto done;
1507
+                }
1508
+
1509
+                // Get the last indicator.
1510
+                json_object *indicator_obj = json_object_array_get_idx(indicators, num_indicators - 1);
1511
+                if (NULL == indicator_obj) {
1512
+                    cli_errmsg("cli_virus_found_cb: Failed to get last indicator from Indicators array.\n");
1513
+                    status = CL_ERROR;
1514
+                    goto done;
1515
+                }
1516
+
1517
+                // Add an "Ignored" string to the indicator object.
1518
+                json_object *ignored = json_object_new_string("Signature ignored by alert application callback");
1519
+                if (!ignored) {
1520
+                    cli_errmsg("metadata_json_trust_this_layer: no memory for json ignored indicator object\n");
1521
+                    status = CL_EMEM;
1522
+                    goto done;
1523
+                }
1524
+                json_ret = json_object_object_add(indicator_obj, "Ignored", ignored);
1525
+                if (0 != json_ret) {
1526
+                    cli_errmsg("metadata_json_trust_this_layer: Failed to add Ignored boolean to indicator object\n");
1527
+                    status = CL_ERROR;
1528
+                    goto done;
1529
+                }
1484 1530
             }
1485
-            json_object_array_add(arrobj, virobj);
1486
-            json_object_object_add(ctx->this_layer_metadata_json, "IgnoredAlerts", arrobj);
1487 1531
         }
1488 1532
     }
1489 1533
 
... ...
@@ -1513,23 +1557,13 @@ static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType
1513 1513
         // evidence storage for this layer not initialized, initialize a new evidence store.
1514 1514
         ctx->recursion_stack[ctx->recursion_level].evidence = evidence_new();
1515 1515
         if (NULL == ctx->recursion_stack[ctx->recursion_level].evidence) {
1516
-            cli_errmsg("cli_append_virus: no memory for evidence store\n");
1516
+            cli_errmsg("append_virus: no memory for evidence store\n");
1517 1517
             status = CL_EMEM;
1518 1518
             goto done;
1519 1519
         }
1520 1520
     }
1521 1521
     ctx->this_layer_evidence = ctx->recursion_stack[ctx->recursion_level].evidence;
1522 1522
 
1523
-    if ((ctx->fmap != NULL) &&
1524
-        (ctx->recursion_stack != NULL)) {
1525
-
1526
-        status = cli_check_fp(ctx, virname);
1527
-        if (CL_VERIFIED == status) {
1528
-            // FP signature found for one of the layers. Ignore indicator.
1529
-            goto done;
1530
-        }
1531
-    }
1532
-
1533 1523
     add_successful = evidence_add_indicator(
1534 1524
         ctx->this_layer_evidence,
1535 1525
         virname,
... ...
@@ -1542,6 +1576,78 @@ static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType
1542 1542
         goto done;
1543 1543
     }
1544 1544
 
1545
+    if (SCAN_COLLECT_METADATA && ctx->this_layer_metadata_json) {
1546
+        // Add the indicator to the metadata.
1547
+        json_object *indicators = NULL;
1548
+        if (!json_object_object_get_ex(ctx->this_layer_metadata_json, "Indicators", &indicators)) {
1549
+            indicators = json_object_new_array();
1550
+            if (NULL == indicators) {
1551
+                cli_errmsg("append_virus: no memory for json Indicators array\n");
1552
+            } else {
1553
+                json_object_object_add(ctx->this_layer_metadata_json, "Indicators", indicators);
1554
+            }
1555
+        }
1556
+
1557
+        // Create json object containing name, type, depth, and object_id
1558
+        json_object *indicator_obj = json_object_new_object();
1559
+        if (NULL == indicator_obj) {
1560
+            cli_errmsg("append_virus: no memory for json indicator object\n");
1561
+        } else {
1562
+            json_object_object_add(indicator_obj, "Name", json_object_new_string(virname));
1563
+            switch (type) {
1564
+                case IndicatorType_Strong: {
1565
+                    json_object_object_add(indicator_obj, "Type", json_object_new_string("Strong"));
1566
+                } break;
1567
+                case IndicatorType_PotentiallyUnwanted: {
1568
+                    json_object_object_add(indicator_obj, "Type", json_object_new_string("PotentiallyUnwanted"));
1569
+                } break;
1570
+                case IndicatorType_Weak: {
1571
+                    json_object_object_add(indicator_obj, "Type", json_object_new_string("Weak"));
1572
+                } break;
1573
+            }
1574
+            json_object_object_add(indicator_obj, "Depth", json_object_new_uint64((uint64_t)0)); // 0 for this layer
1575
+            json_object_object_add(indicator_obj, "ObjectID", json_object_new_uint64((uint64_t)ctx->recursion_stack[ctx->recursion_level].object_id));
1576
+            json_object_array_add(indicators, indicator_obj);
1577
+        }
1578
+
1579
+        // If this is a strong or potentially unwanted indicator, we add it to the "Alerts" array.
1580
+        if (type != IndicatorType_Weak) {
1581
+            json_object *arrobj = NULL;
1582
+            if (!json_object_object_get_ex(ctx->this_layer_metadata_json, "Alerts", &arrobj)) {
1583
+                arrobj = json_object_new_array();
1584
+                if (NULL == arrobj) {
1585
+                    cli_errmsg("append_virus: no memory for json virus array\n");
1586
+                    status = CL_EMEM;
1587
+                    goto done;
1588
+                }
1589
+                json_object_object_add(ctx->this_layer_metadata_json, "Alerts", arrobj);
1590
+            }
1591
+
1592
+            // Increment the indicator_obj reference count, so that it can be added to the "Alerts" array.
1593
+            json_object_get(indicator_obj);
1594
+
1595
+            // Add the same indicator object to the "Alerts" array.
1596
+            json_object_array_add(arrobj, indicator_obj);
1597
+        }
1598
+    }
1599
+
1600
+    // Check for false positive hash signature matches for the current and parent layers.
1601
+    // Do this after running the virus callback, so that the callback always gets called.
1602
+    // Also do this after adding metadata, so that the metadata will correctly show ignored alerts.
1603
+    if (ctx->fmap != NULL) {
1604
+        // Check for ctx->fmap is because `do_phishing_test()` in the `check_regex.c` unit tests
1605
+        // calls append_virus() through cli_append_potentially_unwanted() without actually providing
1606
+        // an fmap.
1607
+        // TODO: Add a basic fmap for unit tests since it makes no sense to append alerts when your scan
1608
+        //       context doesn't even have an fmap.
1609
+
1610
+        status = cli_check_fp(ctx, virname);
1611
+        if (CL_VERIFIED == status) {
1612
+            // FP signature found for one of the layers. Ignore indicator.
1613
+            goto done;
1614
+        }
1615
+    }
1616
+
1545 1617
     if (type == IndicatorType_Strong) {
1546 1618
         // Run the virus callbacks which in clamscan says "<signature name> FOUND"
1547 1619
         callback_ret = cli_virus_found_cb(ctx, virname, type);
... ...
@@ -1570,50 +1676,7 @@ static cl_error_t append_virus(cli_ctx *ctx, const char *virname, IndicatorType
1570 1570
             ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_POTENTIALLY_UNWANTED;
1571 1571
         }
1572 1572
     } else if (type == IndicatorType_Weak) {
1573
-        cli_dbgmsg("cli_append_virus: Weak indicator '%s' added to evidence\n", virname);
1574
-    }
1575
-
1576
-    if (SCAN_COLLECT_METADATA && ctx->this_layer_metadata_json) {
1577
-        if (type == IndicatorType_Weak) {
1578
-            // If this is a weak indicator, we don't add it to the "Alerts" array.
1579
-            // Instead, we add it to the "WeakIndicators" array.
1580
-            json_object *arrobj, *virobj;
1581
-            if (!json_object_object_get_ex(ctx->this_layer_metadata_json, "WeakIndicators", &arrobj)) {
1582
-                arrobj = json_object_new_array();
1583
-                if (NULL == arrobj) {
1584
-                    cli_errmsg("cli_append_virus: no memory for json weak indicators array\n");
1585
-                    status = CL_EMEM;
1586
-                    goto done;
1587
-                }
1588
-                json_object_object_add(ctx->this_layer_metadata_json, "WeakIndicators", arrobj);
1589
-            }
1590
-            virobj = json_object_new_string(virname);
1591
-            if (NULL == virobj) {
1592
-                cli_errmsg("cli_append_virus: no memory for json weak indicator name object\n");
1593
-                status = CL_EMEM;
1594
-                goto done;
1595
-            }
1596
-            json_object_array_add(arrobj, virobj);
1597
-        } else {
1598
-            // If this is a strong or potentially unwanted indicator, we add it to the "Alerts" array.
1599
-            json_object *arrobj, *virobj;
1600
-            if (!json_object_object_get_ex(ctx->this_layer_metadata_json, "Alerts", &arrobj)) {
1601
-                arrobj = json_object_new_array();
1602
-                if (NULL == arrobj) {
1603
-                    cli_errmsg("cli_append_virus: no memory for json virus array\n");
1604
-                    status = CL_EMEM;
1605
-                    goto done;
1606
-                }
1607
-                json_object_object_add(ctx->this_layer_metadata_json, "Alerts", arrobj);
1608
-            }
1609
-            virobj = json_object_new_string(virname);
1610
-            if (NULL == virobj) {
1611
-                cli_errmsg("cli_append_virus: no memory for json virus name object\n");
1612
-                status = CL_EMEM;
1613
-                goto done;
1614
-            }
1615
-            json_object_array_add(arrobj, virobj);
1616
-        }
1573
+        cli_dbgmsg("append_virus: Weak indicator '%s' added to evidence\n", virname);
1617 1574
     }
1618 1575
 
1619 1576
     if (callback_ret == CL_BREAK) {
... ...
@@ -1930,6 +1993,103 @@ done:
1930 1930
     return status;
1931 1931
 }
1932 1932
 
1933
+/**
1934
+ * @brief Copy indicators from a child JSON object to a parent JSON object.
1935
+ *
1936
+ * Used to copy indicators and alerts from a child layer to a parent layer in the recursion stack.
1937
+ * Will increment the Depth field in the indicators to reflect the new layer depth.
1938
+ *
1939
+ * @param parent        The parent JSON object to which indicators will be added.
1940
+ * @param child         The child JSON object from which indicators will be copied.
1941
+ * @param array_name    The name of the array in the JSON object where indicators are stored (e.g., "Indicators", "Alerts").
1942
+ * @return cl_error_t   CL_SUCCESS if successful, or an error code if something went wrong.
1943
+ */
1944
+static cl_error_t json_add_child_array(json_object *parent, json_object *child, const char *array_name)
1945
+{
1946
+    cl_error_t status = CL_SUCCESS;
1947
+
1948
+    json_object *child_array = NULL;
1949
+
1950
+    if (0 == json_object_object_get_ex(child, array_name, &child_array)) {
1951
+        cli_dbgmsg("cli_recursion_stack_pop: no %s array in child object\n", array_name);
1952
+        status = CL_SUCCESS;
1953
+        goto done;
1954
+    }
1955
+
1956
+    /*
1957
+     * Found the array. Copy each element to the parent layer and increment the field named "Depth".
1958
+     */
1959
+
1960
+    /* Get the parent layer array. Create a new one if it doesn't exist */
1961
+    json_object *parent_layer_indicators = NULL;
1962
+    if (!json_object_object_get_ex(parent, array_name, &parent_layer_indicators)) {
1963
+        parent_layer_indicators = json_object_new_array();
1964
+        if (NULL == parent_layer_indicators) {
1965
+            cli_errmsg("cli_recursion_stack_pop: no memory for json Indicators array\n");
1966
+            status = CL_ERROR;
1967
+            goto done;
1968
+        }
1969
+
1970
+        if (json_object_object_add(parent, array_name, parent_layer_indicators)) {
1971
+            cli_errmsg("cli_recursion_stack_pop: failed to add json Indicators array to parent object\n");
1972
+            status = CL_ERROR;
1973
+            goto done;
1974
+        }
1975
+    }
1976
+
1977
+    /* Get the number of indicators in this layer */
1978
+    size_t num_indicators = json_object_array_length(child_array);
1979
+    size_t i;
1980
+
1981
+    /* Copy all indicators from this layer to the parent layer */
1982
+    for (i = 0; i < num_indicators; i++) {
1983
+        json_object *indicator = json_object_array_get_idx(child_array, i);
1984
+        if (NULL == indicator) {
1985
+            cli_errmsg("cli_recursion_stack_pop: Failed to get indicator at index %zu\n", i);
1986
+            status = CL_ERROR;
1987
+            goto done;
1988
+        }
1989
+
1990
+        // Check if the indicator is a valid JSON object
1991
+        if (!json_object_is_type(indicator, json_type_object)) {
1992
+            continue; // Skip non-object indicators
1993
+        }
1994
+
1995
+        /* Make a new object for the copy, because we need to increment the Depth field */
1996
+        json_object *indicator_copy = json_object_new_object();
1997
+        if (NULL == indicator_copy) {
1998
+            cli_errmsg("cli_recursion_stack_pop: no memory for json indicator copy\n");
1999
+            status = CL_EMEM;
2000
+            goto done;
2001
+        }
2002
+
2003
+        /* Copy the indicator's properties to the new object */
2004
+        json_object_object_foreach(indicator, key, val)
2005
+        {
2006
+            if (strcmp(key, "Depth") == 0) {
2007
+                /* Depth is a new int object with incremented value */
2008
+                json_object *new_depth = json_object_new_int(json_object_get_int(val) + 1);
2009
+                if (NULL == new_depth) {
2010
+                    cli_errmsg("cli_recursion_stack_pop: no memory for json new_depth\n");
2011
+                    status = CL_EMEM;
2012
+                    goto done;
2013
+                }
2014
+                json_object_object_add(indicator_copy, key, new_depth);
2015
+            } else {
2016
+                /* All other fields are shallow copied. Just need to increment the reference count */
2017
+                json_object_get(val);
2018
+                json_object_object_add(indicator_copy, key, val);
2019
+            }
2020
+        }
2021
+
2022
+        /* Add the copied indicator to the parent layer's indicators */
2023
+        json_object_array_add(parent_layer_indicators, indicator_copy);
2024
+    }
2025
+
2026
+done:
2027
+    return status;
2028
+}
2029
+
1933 2030
 cl_fmap_t *cli_recursion_stack_pop(cli_ctx *ctx)
1934 2031
 {
1935 2032
     cl_fmap_t *popped_map = NULL;
... ...
@@ -1941,113 +2101,9 @@ cl_fmap_t *cli_recursion_stack_pop(cli_ctx *ctx)
1941 1941
 
1942 1942
     /* If evidence (i.e. a collection of indicators / matches) were found for the popped layer, add it to the parents evidence */
1943 1943
     if (ctx->recursion_stack[ctx->recursion_level].evidence) {
1944
-
1945 1944
         /*
1946 1945
          * Record contained matches in the parent layer's evidence.
1947 1946
          */
1948
-        if (SCAN_COLLECT_METADATA) {
1949
-            size_t num_indicators;
1950
-            size_t i;
1951
-            json_object *parent_object = ctx->recursion_stack[ctx->recursion_level - 1].metadata_json;
1952
-
1953
-            /* Get "ContainedIndicators" array */
1954
-            json_object *contained_indicators = NULL;
1955
-            if (!json_object_object_get_ex(parent_object, "ContainedIndicators", &contained_indicators)) {
1956
-                contained_indicators = json_object_new_array();
1957
-                if (NULL == contained_indicators) {
1958
-                    cli_errmsg("cli_recursion_stack_pop: no memory for json ContainedIndicators array\n");
1959
-                } else {
1960
-                    json_object_object_add(parent_object, "ContainedIndicators", contained_indicators);
1961
-                }
1962
-            }
1963
-
1964
-            if (NULL != contained_indicators) {
1965
-                /* Get each Strong indicator and add it */
1966
-                num_indicators = evidence_num_indicators_type(
1967
-                    ctx->this_layer_evidence,
1968
-                    IndicatorType_Strong);
1969
-                for (i = 0; i < num_indicators; i++) {
1970
-                    size_t depth, object_id;
1971
-                    const char *indicator = evidence_get_indicator(
1972
-                        ctx->this_layer_evidence,
1973
-                        IndicatorType_Strong,
1974
-                        i,
1975
-                        &depth,
1976
-                        &object_id);
1977
-
1978
-                    if (NULL != indicator) {
1979
-                        // Create json object containing name, type, depth, and object_id
1980
-                        json_object *match_obj = json_object_new_object();
1981
-                        if (NULL == match_obj) {
1982
-                            cli_errmsg("cli_recursion_stack_pop: no memory for json match object\n");
1983
-                        } else {
1984
-                            json_object_object_add(match_obj, "Name", json_object_new_string(indicator));
1985
-                            json_object_object_add(match_obj, "Type", json_object_new_string("Strong"));
1986
-                            json_object_object_add(match_obj, "Depth", json_object_new_uint64((uint64_t)depth + 1)); // depth + 1 because this is a child of the parent layer
1987
-                            json_object_object_add(match_obj, "ObjectID", json_object_new_uint64((uint64_t)object_id));
1988
-                            json_object_array_add(contained_indicators, match_obj);
1989
-                        }
1990
-                    }
1991
-                }
1992
-
1993
-                /* Get each Potentially Unwanted indicator and add it */
1994
-                num_indicators = evidence_num_indicators_type(
1995
-                    ctx->this_layer_evidence,
1996
-                    IndicatorType_PotentiallyUnwanted);
1997
-                for (i = 0; i < num_indicators; i++) {
1998
-                    size_t depth, object_id;
1999
-                    const char *indicator = evidence_get_indicator(
2000
-                        ctx->this_layer_evidence,
2001
-                        IndicatorType_PotentiallyUnwanted,
2002
-                        i,
2003
-                        &depth,
2004
-                        &object_id);
2005
-
2006
-                    if (NULL != indicator) {
2007
-                        // Create json object containing name, type, depth, and object_id
2008
-                        json_object *match_obj = json_object_new_object();
2009
-                        if (NULL == match_obj) {
2010
-                            cli_errmsg("cli_recursion_stack_pop: no memory for json match object\n");
2011
-                        } else {
2012
-                            json_object_object_add(match_obj, "Name", json_object_new_string(indicator));
2013
-                            json_object_object_add(match_obj, "Type", json_object_new_string("PotentiallyUnwanted"));
2014
-                            json_object_object_add(match_obj, "Depth", json_object_new_uint64((uint64_t)depth + 1)); // depth + 1 because this is a child of the parent layer
2015
-                            json_object_object_add(match_obj, "ObjectID", json_object_new_uint64((uint64_t)object_id));
2016
-                            json_object_array_add(contained_indicators, match_obj);
2017
-                        }
2018
-                    }
2019
-                }
2020
-
2021
-                /* Get each Weak indicator and add it */
2022
-                num_indicators = evidence_num_indicators_type(
2023
-                    ctx->this_layer_evidence,
2024
-                    IndicatorType_Weak);
2025
-                for (i = 0; i < num_indicators; i++) {
2026
-                    size_t depth, object_id;
2027
-                    const char *indicator = evidence_get_indicator(
2028
-                        ctx->this_layer_evidence,
2029
-                        IndicatorType_Weak,
2030
-                        i,
2031
-                        &depth,
2032
-                        &object_id);
2033
-
2034
-                    if (NULL != indicator) {
2035
-                        // Create json object containing name, type, depth, and object_id
2036
-                        json_object *match_obj = json_object_new_object();
2037
-                        if (NULL == match_obj) {
2038
-                            cli_errmsg("cli_recursion_stack_pop: no memory for json match object\n");
2039
-                        } else {
2040
-                            json_object_object_add(match_obj, "Name", json_object_new_string(indicator));
2041
-                            json_object_object_add(match_obj, "Type", json_object_new_string("Weak"));
2042
-                            json_object_object_add(match_obj, "Depth", json_object_new_uint64((uint64_t)depth + 1)); // depth + 1 because this is a child of the parent layer
2043
-                            json_object_object_add(match_obj, "ObjectID", json_object_new_uint64((uint64_t)object_id));
2044
-                            json_object_array_add(contained_indicators, match_obj);
2045
-                        }
2046
-                    }
2047
-                }
2048
-            }
2049
-        }
2050
-
2051 1947
         if (ctx->recursion_stack[ctx->recursion_level - 1].evidence == NULL) {
2052 1948
             evidence_t parent_evidence   = NULL;
2053 1949
             FFIError *new_evidence_error = NULL;
... ...
@@ -2089,6 +2145,31 @@ cl_fmap_t *cli_recursion_stack_pop(cli_ctx *ctx)
2089 2089
         ctx->recursion_stack[ctx->recursion_level].evidence = NULL;
2090 2090
     }
2091 2091
 
2092
+    if (SCAN_COLLECT_METADATA) {
2093
+        /*
2094
+         * Record contained indicators and alerts in the parent layer's metadata.
2095
+         * Copy the indicators and alerts from this layer to the parent layer.
2096
+         */
2097
+        json_object *this_layer_object = ctx->recursion_stack[ctx->recursion_level].metadata_json;
2098
+        json_object *parent_object     = ctx->recursion_stack[ctx->recursion_level - 1].metadata_json;
2099
+
2100
+        if (this_layer_object && parent_object) {
2101
+            cl_error_t ret;
2102
+
2103
+            // Copy indicators from this layer to the parent layer.
2104
+            ret = json_add_child_array(parent_object, this_layer_object, "Indicators");
2105
+            if (CL_SUCCESS != ret) {
2106
+                cli_errmsg("cli_recursion_stack_pop: Failed to copy Indicators from child to parent: %s\n", cl_strerror(ret));
2107
+            }
2108
+
2109
+            // Copy alerts from this layer to the parent layer.
2110
+            ret = json_add_child_array(parent_object, this_layer_object, "Alerts");
2111
+            if (CL_SUCCESS != ret) {
2112
+                cli_errmsg("cli_recursion_stack_pop: Failed to copy Alerts from child to parent: %s\n", cl_strerror(ret));
2113
+            }
2114
+        }
2115
+    }
2116
+
2092 2117
     if ((ctx->engine->engine_options & ENGINE_OPTIONS_TMPDIR_RECURSION)) {
2093 2118
         /* Delete the layer's temporary directory.
2094 2119
          * Use rmdir to remove empty tmp subdirectories. If rmdir fails, it wasn't empty. */
... ...
@@ -2519,6 +2600,23 @@ void cl_engine_set_scan_callback(struct cl_engine *engine, clcb_scan callback, c
2519 2519
 #define POST_SCAN_NAME "PostScan"
2520 2520
 #define ALERT_NAME "Alert"
2521 2521
 #define FILE_TYPE_NAME "FileType"
2522
+static const char *callback_name(cl_scan_callback_t location)
2523
+{
2524
+    switch (location) {
2525
+        case CL_SCAN_CALLBACK_PRE_HASH:
2526
+            return "pre-hash application callback";
2527
+        case CL_SCAN_CALLBACK_PRE_SCAN:
2528
+            return "pre-scan application callback";
2529
+        case CL_SCAN_CALLBACK_POST_SCAN:
2530
+            return "post-scan application callback";
2531
+        case CL_SCAN_CALLBACK_ALERT:
2532
+            return "alert application callback";
2533
+        case CL_SCAN_CALLBACK_FILE_TYPE:
2534
+            return "file-type application callback";
2535
+        default:
2536
+            return "Unknown";
2537
+    }
2538
+}
2522 2539
 
2523 2540
 cl_error_t cli_dispatch_scan_callback(cli_ctx *ctx, cl_scan_callback_t location)
2524 2541
 {
... ...
@@ -2631,10 +2729,10 @@ cl_error_t cli_dispatch_scan_callback(cli_ctx *ctx, cl_scan_callback_t location)
2631 2631
         case CL_VERIFIED: {
2632 2632
             // An alert callback returning CL_VERIFIED means the application verified the current layer as clean.
2633 2633
             // So we need to remove any alerts for this layer and return CL_VERIFIED (will stop scanning this layer).
2634
-            cli_dbgmsg("dispatch_scan_callback: Layer verified clean by callback\n");
2634
+            cli_dbgmsg("dispatch_scan_callback: Layer trusted by callback\n");
2635 2635
 
2636 2636
             // Remove any evidence for this layer and set the verdict to trusted.
2637
-            (void)cli_trust_this_layer(ctx);
2637
+            (void)cli_trust_this_layer(ctx, callback_name(location));
2638 2638
             status = CL_VERIFIED;
2639 2639
         } break;
2640 2640
 
... ...
@@ -2740,13 +2838,13 @@ uint8_t cli_set_debug_flag(uint8_t debug_flag)
2740 2740
 /**
2741 2741
  * @brief Update the metadata JSON object to reflect that the current layer was trusted.
2742 2742
  *
2743
- * This involves renaming the "ContainedIndicators" array to "IgnoredIndicators" and the "Alerts" array to "IgnoredAlerts".
2744
- * It also recursively processes any contained or embedded objects to rename their arrays as well.
2743
+ * This involves deleting "Alerts" arrays and adding "Ignored" keys to the affected "Indicators".
2744
+ * This function recursively processes any contained or embedded objects to do the same for them.
2745 2745
  *
2746 2746
  * @param scan_layer_json   The JSON object representing the current scan layer's metadata.
2747 2747
  * @return cl_error_t       CL_SUCCESS on success, or an error code on failure.
2748 2748
  */
2749
-static cl_error_t metadata_json_trust_this_layer(json_object *scan_layer_json)
2749
+static cl_error_t metadata_json_trust_this_layer(json_object *scan_layer_json, const char *reason)
2750 2750
 {
2751 2751
     cl_error_t status = CL_ERROR;
2752 2752
     cl_error_t ret;
... ...
@@ -2758,24 +2856,31 @@ static cl_error_t metadata_json_trust_this_layer(json_object *scan_layer_json)
2758 2758
         goto done;
2759 2759
     }
2760 2760
 
2761
-    // Trust the current layer's metadata by renaming the "ContainedIndicators" and "Alerts" arrays.
2762
-    json_object *contained_indicators = NULL;
2763
-    if (json_object_object_get_ex(scan_layer_json, "ContainedIndicators", &contained_indicators)) {
2764
-        // Rename "ContainedIndicators" to "IgnoredIndicators" to indicate these indicators were ignored because the layer was trusted.
2765
-        json_ret = json_object_object_add(scan_layer_json, "IgnoredIndicators", contained_indicators);
2766
-        if (json_ret != 0) {
2767
-            cli_errmsg("metadata_json_trust_this_layer: failed to rename ContainedIndicators to IgnoredIndicators in metadata JSON\n");
2768
-            status = CL_ERROR;
2769
-            goto done;
2761
+    // Trust the current layer's metadata by renaming the "Indicators" and "Alerts" arrays.
2762
+    json_object *indicators = NULL;
2763
+    if (json_object_object_get_ex(scan_layer_json, "Indicators", &indicators)) {
2764
+        // For each indicator in the array, add the "Ignored" string and set to the "reason".
2765
+        size_t num_indicators = json_object_array_length(indicators);
2766
+        size_t i;
2767
+        for (i = 0; i < num_indicators; i++) {
2768
+            json_object *indicator = json_object_array_get_idx(indicators, i);
2769
+            if (indicator) {
2770
+                json_object *ignored = json_object_new_string(reason);
2771
+                if (!ignored) {
2772
+                    cli_errmsg("metadata_json_trust_this_layer: no memory for json ignored indicator object\n");
2773
+                    status = CL_EMEM;
2774
+                    goto done;
2775
+                }
2776
+                json_ret = json_object_object_add(indicator, "Ignored", ignored);
2777
+                if (0 != json_ret) {
2778
+                    cli_errmsg("metadata_json_trust_this_layer: Failed to add Ignored boolean to indicator object\n");
2779
+                    status = CL_ERROR;
2780
+                    goto done;
2781
+                }
2782
+            }
2770 2783
         }
2771 2784
 
2772
-        // Increment the reference count of the "ContainedIndicators" array since json_object_object_add() does not do that.
2773
-        json_object_get(contained_indicators);
2774
-
2775
-        // Remove the original "ContainedIndicators" entry.
2776
-        json_object_object_del(scan_layer_json, "ContainedIndicators");
2777
-
2778
-        // Now recursively find any contained objects and rename their "ContainedIndicators" arrays too.
2785
+        // Now recursively find any contained objects and rename their "Indicators" arrays too.
2779 2786
         json_object *contained_objects = NULL;
2780 2787
         if (json_object_object_get_ex(scan_layer_json, "ContainedObjects", &contained_objects)) {
2781 2788
             size_t i;
... ...
@@ -2783,7 +2888,7 @@ static cl_error_t metadata_json_trust_this_layer(json_object *scan_layer_json)
2783 2783
             for (i = 0; i < num_objects; i++) {
2784 2784
                 json_object *contained_object = json_object_array_get_idx(contained_objects, i);
2785 2785
                 if (contained_object) {
2786
-                    ret = metadata_json_trust_this_layer(contained_object);
2786
+                    ret = metadata_json_trust_this_layer(contained_object, reason);
2787 2787
                     if (ret != CL_SUCCESS) {
2788 2788
                         cli_errmsg("metadata_json_trust_this_layer: failed to update metadata JSON for contained object: %s\n", cl_strerror(ret));
2789 2789
                     }
... ...
@@ -2799,7 +2904,7 @@ static cl_error_t metadata_json_trust_this_layer(json_object *scan_layer_json)
2799 2799
             for (i = 0; i < num_objects; i++) {
2800 2800
                 json_object *embedded_object = json_object_array_get_idx(embedded_objects, i);
2801 2801
                 if (embedded_object) {
2802
-                    ret = metadata_json_trust_this_layer(embedded_object);
2802
+                    ret = metadata_json_trust_this_layer(embedded_object, reason);
2803 2803
                     if (ret != CL_SUCCESS) {
2804 2804
                         cli_errmsg("metadata_json_trust_this_layer: failed to update metadata JSON for embedded object: %s\n", cl_strerror(ret));
2805 2805
                     }
... ...
@@ -2808,22 +2913,8 @@ static cl_error_t metadata_json_trust_this_layer(json_object *scan_layer_json)
2808 2808
         }
2809 2809
     }
2810 2810
 
2811
-    json_object *viruses = NULL;
2812
-    if (json_object_object_get_ex(scan_layer_json, "Alerts", &viruses)) {
2813
-        // Rename "Alerts" to "IgnoredAlerts" to indicate these alerts were ignored because the layer was trusted.
2814
-        ret = json_object_object_add(scan_layer_json, "IgnoredAlerts", viruses);
2815
-        if (ret != 0) {
2816
-            cli_errmsg("metadata_json_trust_this_layer: failed to rename Alerts to IgnoredAlerts in metadata JSON\n");
2817
-            status = CL_ERROR;
2818
-            goto done;
2819
-        }
2820
-
2821
-        // Increment the reference count of the "Alerts" array since json_object_object_add() does not do that.
2822
-        json_object_get(viruses);
2823
-
2824
-        // Remove the original "Alerts" entry.
2825
-        json_object_object_del(scan_layer_json, "Alerts");
2826
-    }
2811
+    // Remove the "Alerts" entry.
2812
+    json_object_object_del(scan_layer_json, "Alerts");
2827 2813
 
2828 2814
     status = CL_SUCCESS;
2829 2815
 
... ...
@@ -2831,10 +2922,13 @@ done:
2831 2831
     return status;
2832 2832
 }
2833 2833
 
2834
-cl_error_t cli_trust_this_layer(cli_ctx *ctx)
2834
+cl_error_t cli_trust_this_layer(cli_ctx *ctx, const char *source)
2835 2835
 {
2836 2836
     cl_error_t status = CL_ERROR;
2837 2837
 
2838
+    char *reason      = NULL;
2839
+    size_t reason_len = 0;
2840
+
2838 2841
     if (!ctx) {
2839 2842
         cli_errmsg("cli_trust_this_layer: invalid context\n");
2840 2843
         status = CL_ENULLARG;
... ...
@@ -2850,7 +2944,17 @@ cl_error_t cli_trust_this_layer(cli_ctx *ctx)
2850 2850
     ctx->recursion_stack[ctx->recursion_level].verdict = CL_VERDICT_TRUSTED;
2851 2851
 
2852 2852
     if (SCAN_COLLECT_METADATA && ctx->this_layer_metadata_json) {
2853
-        status = metadata_json_trust_this_layer(ctx->this_layer_metadata_json);
2853
+        reason_len = strlen("Object ") + SIZE_T_CHARLEN + strlen(" trusted by ") + strlen(source) + 1;
2854
+        reason     = malloc(reason_len);
2855
+        if (!reason) {
2856
+            cli_errmsg("cli_trust_this_layer: no memory for reason string\n");
2857
+            status = CL_EMEM;
2858
+            goto done;
2859
+        }
2860
+        snprintf(reason, reason_len, "Object %zu trusted by %s",
2861
+                 ctx->recursion_stack[ctx->recursion_level].object_id, source);
2862
+
2863
+        status = metadata_json_trust_this_layer(ctx->this_layer_metadata_json, reason);
2854 2864
         if (status != CL_SUCCESS) {
2855 2865
             cli_errmsg("cli_trust_this_layer: failed to update metadata JSON to reflect trusted layer: %s\n", cl_strerror(status));
2856 2866
             goto done;
... ...
@@ -2860,14 +2964,20 @@ cl_error_t cli_trust_this_layer(cli_ctx *ctx)
2860 2860
     status = CL_SUCCESS;
2861 2861
 
2862 2862
 done:
2863
+
2864
+    CLI_FREE_AND_SET_NULL(reason);
2865
+
2863 2866
     return status;
2864 2867
 }
2865 2868
 
2866
-cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer)
2869
+cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer, const char *source)
2867 2870
 {
2868 2871
     cl_error_t status = CL_ERROR;
2869 2872
     size_t i;
2870 2873
 
2874
+    char *reason      = NULL;
2875
+    size_t reason_len = 0;
2876
+
2871 2877
     if (!ctx) {
2872 2878
         cli_errmsg("cli_trust_layers: invalid context\n");
2873 2879
         status = CL_ENULLARG;
... ...
@@ -2883,10 +2993,30 @@ cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_lay
2883 2883
         }
2884 2884
 
2885 2885
         ctx->recursion_stack[i].verdict = CL_VERDICT_TRUSTED;
2886
+
2887
+        if (SCAN_COLLECT_METADATA && ctx->recursion_stack[i].metadata_json) {
2888
+            reason_len = strlen("Object ") + SIZE_T_CHARLEN + strlen(" trusted by ") + strlen(source) + 1;
2889
+            reason     = malloc(reason_len);
2890
+            if (!reason) {
2891
+                cli_errmsg("dispatch_scan_callback: no memory for reason string\n");
2892
+                return CL_EMEM;
2893
+            }
2894
+            snprintf(reason, reason_len, "Object %zu trusted by %s",
2895
+                     ctx->recursion_stack[ctx->recursion_level].object_id, source);
2896
+
2897
+            status = metadata_json_trust_this_layer(ctx->recursion_stack[i].metadata_json, reason);
2898
+            if (status != CL_SUCCESS) {
2899
+                cli_errmsg("cli_trust_this_layer: failed to update metadata JSON to reflect trusted layer: %s\n", cl_strerror(status));
2900
+                goto done;
2901
+            }
2902
+        }
2886 2903
     }
2887 2904
 
2888 2905
     status = CL_SUCCESS;
2889 2906
 
2890 2907
 done:
2908
+
2909
+    CLI_FREE_AND_SET_NULL(reason);
2910
+
2891 2911
     return status;
2892 2912
 }
... ...
@@ -1327,9 +1327,10 @@ uint8_t cli_set_debug_flag(uint8_t debug_flag);
1327 1327
  * @brief Trust the current layer by removing any evidence and setting the verdict to trusted.
1328 1328
  *
1329 1329
  * @param ctx           The scan context.
1330
+ * @param source        The source of the trust request.
1330 1331
  * @return cl_error_t   CL_SUCCESS on success, or an error code.
1331 1332
  */
1332
-cl_error_t cli_trust_this_layer(cli_ctx *ctx);
1333
+cl_error_t cli_trust_this_layer(cli_ctx *ctx, const char *source);
1333 1334
 
1334 1335
 /**
1335 1336
  * @brief Trust a range of layers by removing any evidence and setting the verdict to trusted.
... ...
@@ -1337,9 +1338,10 @@ cl_error_t cli_trust_this_layer(cli_ctx *ctx);
1337 1337
  * @param ctx           The scan context.
1338 1338
  * @param start_layer   The layer to start trusting from (inclusive).
1339 1339
  * @param end_layer     The layer to stop trusting at (inclusive).
1340
+ * @param source        The source of the trust request.
1340 1341
  * @return cl_error_t   CL_SUCCESS on success, or an error code.
1341 1342
  */
1342
-cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer);
1343
+cl_error_t cli_trust_layers(cli_ctx *ctx, uint32_t start_layer, uint32_t end_layer, const char *source);
1343 1344
 
1344 1345
 #ifndef CLI_SAFER_STRDUP_OR_GOTO_DONE
1345 1346
 /**
... ...
@@ -5455,6 +5455,9 @@ cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo)
5455 5455
     uint32_t sec_dir_size;
5456 5456
     struct cli_exe_info _peinfo;
5457 5457
 
5458
+    char *source      = NULL;
5459
+    size_t source_len = 0;
5460
+
5458 5461
     // If Authenticode parsing has been disabled via DCONF or an engine
5459 5462
     // option, then don't continue on.
5460 5463
     if (!(DCONF & PE_CONF_CERTS))
... ...
@@ -5656,8 +5659,18 @@ cl_error_t cli_check_auth_header(cli_ctx *ctx, struct cli_exe_info *peinfo)
5656 5656
         if (cli_hm_scan(authsha, 2, NULL, ctx->engine->hm_fp, hashtype) == CL_VIRUS) {
5657 5657
             cli_dbgmsg("cli_check_auth_header: PE file trusted by catalog file (%s)\n", hashctx_name);
5658 5658
 
5659
+            source_len = strlen("authenticode catalog file: ") + strlen(hashctx_name) + 1;
5660
+            source     = malloc(source_len);
5661
+            if (!source) {
5662
+                cli_errmsg("dispatch_scan_callback: no memory for source string\n");
5663
+                goto finish;
5664
+            }
5665
+            snprintf(source, source_len, "authenticode catalog file: %s", hashctx_name);
5666
+
5659 5667
             // Remove any evidence for this layer and set the verdict to trusted.
5660
-            (void)cli_trust_this_layer(ctx);
5668
+            (void)cli_trust_this_layer(ctx, source);
5669
+
5670
+            CLI_FREE_AND_SET_NULL(source);
5661 5671
 
5662 5672
             ret = CL_VERIFIED;
5663 5673
             goto finish;
... ...
@@ -5679,6 +5692,9 @@ finish:
5679 5679
     if (&_peinfo == peinfo) {
5680 5680
         cli_exe_info_destroy(peinfo);
5681 5681
     }
5682
+
5683
+    CLI_FREE_AND_SET_NULL(source);
5684
+
5682 5685
     return ret;
5683 5686
 }
5684 5687
 
... ...
@@ -4347,7 +4347,7 @@ static cl_error_t dispatch_file_inspection_callback(clcb_file_inspection cb, cli
4347 4347
             cli_dbgmsg("dispatch_file_inspection_callback: file trusted by callback\n");
4348 4348
 
4349 4349
             // Remove any evidence for this layer and set the verdict to trusted.
4350
-            (void)cli_trust_this_layer(ctx);
4350
+            (void)cli_trust_this_layer(ctx, "legacy file-inspection application callback");
4351 4351
 
4352 4352
             break;
4353 4353
         case CL_VIRUS:
... ...
@@ -4371,7 +4371,7 @@ done:
4371 4371
     return status;
4372 4372
 }
4373 4373
 
4374
-static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, const char *filetype)
4374
+static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, const char *filetype, bool pre_cache)
4375 4375
 {
4376 4376
     cl_error_t status = CL_SUCCESS;
4377 4377
     cl_error_t append_ret;
... ...
@@ -4382,21 +4382,28 @@ static cl_error_t dispatch_prescan_callback(clcb_pre_scan cb, cli_ctx *ctx, cons
4382 4382
         perf_stop(ctx, PERFT_PRECB);
4383 4383
 
4384 4384
         switch (status) {
4385
-            case CL_BREAK:
4385
+            case CL_BREAK: {
4386
+                const char *source = pre_cache ? "legacy pre-cache application callback"
4387
+                                               : "legacy pre-scan application callback";
4388
+
4386 4389
                 cli_dbgmsg("dispatch_prescan_callback: file allowed by callback\n");
4387 4390
 
4388 4391
                 // Remove any evidence for this layer and set the verdict to trusted.
4389
-                (void)cli_trust_this_layer(ctx);
4392
+                (void)cli_trust_this_layer(ctx, source);
4390 4393
 
4391 4394
                 status = CL_VERIFIED;
4392
-                break;
4393
-            case CL_VIRUS:
4395
+            } break;
4396
+            case CL_VIRUS: {
4397
+                const char *alert_name = pre_cache ? "Detected.By.Callback.PreCache"
4398
+                                                   : "Detected.By.Callback.PreScan";
4399
+
4394 4400
                 cli_dbgmsg("dispatch_prescan_callback: file blocked by callback\n");
4395
-                append_ret = cli_append_virus(ctx, "Detected.By.Callback");
4401
+
4402
+                append_ret = cli_append_virus(ctx, alert_name);
4396 4403
                 if (append_ret == CL_VIRUS) {
4397 4404
                     status = CL_VIRUS;
4398 4405
                 }
4399
-                break;
4406
+            } break;
4400 4407
             case CL_SUCCESS:
4401 4408
                 // No action requested by callback. Keep scanning.
4402 4409
                 break;
... ...
@@ -4642,7 +4649,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
4642 4642
     /*
4643 4643
      * Run the deprecated pre_cache callback.
4644 4644
      */
4645
-    ret = dispatch_prescan_callback(ctx->engine->cb_pre_cache, ctx, filetype);
4645
+    ret = dispatch_prescan_callback(ctx->engine->cb_pre_cache, ctx, filetype, true /* pre_cache */);
4646 4646
     if (CL_VERIFIED == ret || CL_VIRUS == ret) {
4647 4647
         status = ret;
4648 4648
         goto done;
... ...
@@ -4653,11 +4660,6 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
4653 4653
      */
4654 4654
     ret = dispatch_file_inspection_callback(ctx->engine->cb_file_inspection, ctx, filetype);
4655 4655
     if (CL_SUCCESS != ret) {
4656
-        if (ret == CL_VIRUS) {
4657
-            ret = cli_check_fp(ctx, NULL);
4658
-        } else {
4659
-            ret = CL_SUCCESS;
4660
-        }
4661 4656
         status = ret;
4662 4657
         goto done;
4663 4658
     }
... ...
@@ -4683,7 +4685,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
4683 4683
             if (need_hash[hash_type]) {
4684 4684
                 ret = fmap_will_need_hash_later(ctx->fmap, hash_type);
4685 4685
                 if (CL_SUCCESS != ret) {
4686
-                    cli_dbgmsg("cli_check_fp: Failed to set fmap to need the %s hash later\n", cli_hash_name(hash_type));
4686
+                    cli_dbgmsg("cli_magic_scan: Failed to set fmap to need the %s hash later\n", cli_hash_name(hash_type));
4687 4687
                     status = ret;
4688 4688
                     goto done;
4689 4689
                 }
... ...
@@ -4754,7 +4756,7 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
4754 4754
     /*
4755 4755
      * Run the deprecated pre_scan callback.
4756 4756
      */
4757
-    ret = dispatch_prescan_callback(ctx->engine->cb_pre_scan, ctx, filetype);
4757
+    ret = dispatch_prescan_callback(ctx->engine->cb_pre_scan, ctx, filetype, false /* pre_cache */);
4758 4758
     if (CL_VERIFIED == ret || CL_VIRUS == ret) {
4759 4759
         status = ret;
4760 4760
         goto done;
... ...
@@ -5402,10 +5404,10 @@ done:
5402 5402
                 cli_dbgmsg("cli_magic_scan: file allowed by post_scan callback\n");
5403 5403
 
5404 5404
                 // Remove any evidence for this layer and set the verdict to trusted.
5405
-                (void)cli_trust_this_layer(ctx);
5405
+                (void)cli_trust_this_layer(ctx, "legacy post-scan application callback");
5406 5406
 
5407
-                //status = CL_SUCCESS; // Do override the status here.
5408
-                // If status == CL_VIRUS, we'll fix when we look at the verdict.
5407
+                // status = CL_SUCCESS; // Do override the status here.
5408
+                //  If status == CL_VIRUS, we'll fix when we look at the verdict.
5409 5409
                 break;
5410 5410
             case CL_VIRUS:
5411 5411
                 cli_dbgmsg("cli_magic_scan: file blocked by post_scan callback\n");
... ...
@@ -5418,7 +5420,7 @@ done:
5418 5418
                 // No action requested by callback. Keep scanning.
5419 5419
                 break;
5420 5420
             default:
5421
-                //status = CL_SUCCESS; // Do override the status here, just log a warning.
5421
+                // status = CL_SUCCESS; // Do override the status here, just log a warning.
5422 5422
                 cli_warnmsg("cli_magic_scan: ignoring bad return code from post_scan callback\n");
5423 5423
         }
5424 5424
     }
... ...
@@ -859,7 +859,7 @@ static int arj_read_main_header(arj_metadata_t *metadata)
859 859
         goto done;
860 860
     }
861 861
     if (header_size > HEADERSIZE_MAX) {
862
-        cli_dbgmsg("arj_read_header: invalid header_size: %u\n ", header_size);
862
+        cli_dbgmsg("arj_read_header: invalid header_size: %u\n", header_size);
863 863
         ret = FALSE;
864 864
         goto done;
865 865
     }