From 4c13ca820e4fa01ca62ad66c0665ebbee150f87c Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Mon, 25 Sep 2023 08:34:24 +0200
Subject: Handle private and global rules.

---
 src/analysis/scan/context-int.h |  2 +
 src/analysis/scan/context.c     | 48 ++++++++++++++++++++++++
 src/analysis/scan/context.h     |  6 +++
 src/analysis/scan/grammar.y     | 34 ++++++++++++++---
 src/analysis/scan/pattern.c     | 10 ++---
 src/analysis/scan/rule-int.h    |  6 +++
 src/analysis/scan/rule.c        | 83 +++++++++++++++++++++++++++++++++++------
 src/analysis/scan/rule.h        | 15 +++++++-
 src/analysis/scan/scanner.c     | 58 ++++++++++++++++++++++++----
 src/analysis/scan/tokens.l      |  3 ++
 tests/analysis/scan/common.py   |  2 +
 tests/analysis/scan/grammar.py  | 66 ++++++++++++++++++++++++++++++++
 12 files changed, 301 insertions(+), 32 deletions(-)

diff --git a/src/analysis/scan/context-int.h b/src/analysis/scan/context-int.h
index 8a5fbaf..654ecca 100644
--- a/src/analysis/scan/context-int.h
+++ b/src/analysis/scan/context-int.h
@@ -83,6 +83,8 @@ struct _GScanContext
     full_match_tracker_t **full_trackers;   /* Correspondances confirmées  */
     size_t full_count;                      /* Quantité de correspondances */
 
+    bool global;                            /* Validation globale          */
+
     rule_condition_t *conditions;           /* Ensemble de règles suivies  */
     size_t cond_count;                      /* Quantité de ces conditions  */
 
diff --git a/src/analysis/scan/context.c b/src/analysis/scan/context.c
index c016f7e..8a9b600 100644
--- a/src/analysis/scan/context.c
+++ b/src/analysis/scan/context.c
@@ -246,6 +246,8 @@ static void g_scan_context_init(GScanContext *context)
     context->full_trackers = NULL;
     context->full_count = 0;
 
+    context->global = true;
+
     context->conditions = NULL;
     context->cond_count = 0;
 
@@ -751,6 +753,49 @@ bool g_scan_context_has_rule_for_name(const GScanContext *context, const char *n
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : context = mémoire de résultats d'analyse à consulter.        *
+*                                                                             *
+*  Description : Indique le bilan des règles globales.                        *
+*                                                                             *
+*  Retour      : Bilan global des analyses menées.                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_scan_context_has_global_match(const GScanContext *context)
+{
+    bool result;                            /* Bilan global à retourner    */
+
+    result = context->global;
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : context = mémoire de résultats d'analyse à actualiser.       *
+*                global  = bilan global des analyses menées.                  *
+*                                                                             *
+*  Description : Définit le bilan des règles globales.                        *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_scan_context_set_global_match(GScanContext *context, bool global)
+{
+    context->global = global;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : context = mémoire de résultats d'analyse à consulter.        *
 *                name    = désignation de la règle ciblée.                    *
 *                                                                             *
 *  Description : Indique si une correspondance globale a pu être établie.     *
@@ -773,6 +818,9 @@ bool g_scan_context_has_match_for_rule(GScanContext *context, const char *name)
 
     result = false;
 
+    if (!context->global)
+        goto exit;
+
     /* Recherche de la règle visée */
 
     cond = NULL;
diff --git a/src/analysis/scan/context.h b/src/analysis/scan/context.h
index de8a213..7fb3dd4 100644
--- a/src/analysis/scan/context.h
+++ b/src/analysis/scan/context.h
@@ -101,6 +101,12 @@ bool g_scan_context_set_rule_condition(GScanContext *, const char *, GScanExpres
 /* Indique si un nom donné correspond à une règle. */
 bool g_scan_context_has_rule_for_name(const GScanContext *, const char *);
 
+/* Indique le bilan des règles globales. */
+bool g_scan_context_has_global_match(const GScanContext *);
+
+/* Définit le bilan des règles globales. */
+void g_scan_context_set_global_match(GScanContext *, bool);
+
 /* Indique si une correspondance globale a pu être établie. */
 bool g_scan_context_has_match_for_rule(GScanContext *, const char *);
 
diff --git a/src/analysis/scan/grammar.y b/src/analysis/scan/grammar.y
index 25bb536..e1f0e9e 100644
--- a/src/analysis/scan/grammar.y
+++ b/src/analysis/scan/grammar.y
@@ -73,11 +73,9 @@ typedef void *yyscan_t;
         sized_string_t *tmp_masks;          /* Masques associés            */
     } masked;
 
-
+    ScanRuleFlags rule_flags;               /* Fanions pour règle          */
     GScanRule *rule;                        /* Nouvelle règle à intégrer   */
 
-
-
     GScanTokenNode *node;                   /* Bribe de motif à intégrer   */
     GSearchPattern *pattern;                /* Nouveau motif à considérer  */
 
@@ -140,6 +138,7 @@ YY_DECL;
 %token NOCASE "nocase"
 %token FULLWORD "fullword"
 %token PRIVATE "private"
+%token GLOBAL "global"
 
 
 %token HEX_BYTES
@@ -234,6 +233,8 @@ YY_DECL;
 %type <unsigned_integer> UNSIGNED_INTEGER
 %type <sized_cstring> STRING
 
+%type <rule_flags> rule_flags
+%type <rule_flags> rule_flag
 %type <rule> rule
 
 %type <sized_cstring> PLAIN_TEXT
@@ -345,14 +346,35 @@ YY_DECL;
  * Définition de règle.
  */
 
-              rule : RAW_RULE RULE_NAME
+              rule : rule_flags RAW_RULE RULE_NAME
                    {
-                       *built_rule = g_scan_rule_new($2.data);
+                       *built_rule = g_scan_rule_new($1, $3.data);
                        $<rule>$ = *built_rule;
                    }
                    BRACE_IN meta bytes condition BRACE_OUT
                    {
-                       $$ = $<rule>3;
+                       $$ = $<rule>4;
+                   }
+                   ;
+
+
+        rule_flags : /* empty */
+                   {
+                       $$ = SRF_NONE;
+                   }
+                   | rule_flags rule_flag
+                   {
+                       $$ = $1 | $2;
+                   }
+                   ;
+
+         rule_flag : "private"
+                   {
+                       $$ = SRF_PRIVATE;
+                   }
+                   | "global"
+                   {
+                       $$ = SRF_GLOBAL;
                    }
                    ;
 
diff --git a/src/analysis/scan/pattern.c b/src/analysis/scan/pattern.c
index 797f4ac..fe3babc 100644
--- a/src/analysis/scan/pattern.c
+++ b/src/analysis/scan/pattern.c
@@ -295,7 +295,7 @@ char *g_search_pattern_convert_as_text(const GSearchPattern *pattern, GScanConte
 *                padding  = éventuel bourrage initial à placer ou NULL.       *
 *                level    = profondeur actuelle.                              *
 *                fd       = canal d'écriture.                                 *
-*                trailing = impose une virgule finale ?                       *
+*                tail     = décline la pose d'une virgule finale ?            *
 *                                                                             *
 *  Description : Affiche un motif de recherche au format JSON.                *
 *                                                                             *
@@ -305,7 +305,7 @@ char *g_search_pattern_convert_as_text(const GSearchPattern *pattern, GScanConte
 *                                                                             *
 ******************************************************************************/
 
-void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool trailing)
+void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool tail)
 {
     unsigned int i;                         /* Boucle de parcours          */
     GSearchPatternClass *class;             /* Classe à activer            */
@@ -339,10 +339,10 @@ void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext
     for (i = 0; i < level; i++)
         write(fd, padding->data, padding->len);
 
-    if (trailing)
-        write(fd, "},\n", 3);
-    else
+    if (tail)
         write(fd, "}\n", 2);
+    else
+        write(fd, "},\n", 3);
 
 }
 
diff --git a/src/analysis/scan/rule-int.h b/src/analysis/scan/rule-int.h
index b43cba9..1ca2b7f 100644
--- a/src/analysis/scan/rule-int.h
+++ b/src/analysis/scan/rule-int.h
@@ -37,6 +37,8 @@ struct _GScanRule
 {
     GObject parent;                         /* A laisser en premier        */
 
+    ScanRuleFlags flags;                    /* Propriétés de la règle      */
+
     char *name;                             /* Désignation de la règle     */
     fnv64_t name_hash;                      /* Empreinte de la désignation */
 
@@ -56,5 +58,9 @@ struct _GScanRuleClass
 };
 
 
+/* Met en place une règle de détection statique avec motifs. */
+bool g_scan_rule_create(GScanRule *, ScanRuleFlags, const char *);
+
+
 
 #endif  /* _ANALYSIS_SCAN_RULE_INT_H */
diff --git a/src/analysis/scan/rule.c b/src/analysis/scan/rule.c
index a7d7765..68222dd 100644
--- a/src/analysis/scan/rule.c
+++ b/src/analysis/scan/rule.c
@@ -97,6 +97,8 @@ static void g_scan_rule_class_init(GScanRuleClass *klass)
 
 static void g_scan_rule_init(GScanRule *rule)
 {
+    rule->flags = SRF_NONE;
+
     rule->name = NULL;
     rule->name_hash = 0;
 
@@ -159,7 +161,8 @@ static void g_scan_rule_finalize(GScanRule *rule)
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : name = désignation à associer à la future règle.             *
+*  Paramètres  : flags = propriétés particulières à conférer à la règle.      *
+*                name  = désignation à associer à la future règle.            *
 *                                                                             *
 *  Description : Crée une règle de détection statique à l'aide de motifs.     *
 *                                                                             *
@@ -169,12 +172,14 @@ static void g_scan_rule_finalize(GScanRule *rule)
 *                                                                             *
 ******************************************************************************/
 
-GScanRule *g_scan_rule_new(const char *name)
+GScanRule *g_scan_rule_new(ScanRuleFlags flags, const char *name)
 {
     GScanRule *result;                /* Structure à retourner       */
 
     result = g_object_new(G_TYPE_SCAN_RULE, NULL);
 
+    result->flags = flags;
+
     result->name = strdup(name);
     result->name_hash = fnv_64a_hash(name);
 
@@ -185,7 +190,60 @@ GScanRule *g_scan_rule_new(const char *name)
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : rule = règle de détection à compléter.                       *
+*  Paramètres  : rule  = règle de détection à initialiser pleinement.         *
+*                flags = propriétés particulières à conférer à la règle.      *
+*                name  = désignation à associer à la future règle.            *
+*                                                                             *
+*  Description : Met en place une règle de détection statique avec motifs.    *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_scan_rule_create(GScanRule *rule, ScanRuleFlags flags, const char *name)
+{
+    GScanRule *result;                /* Structure à retourner       */
+
+    result = g_object_new(G_TYPE_SCAN_RULE, NULL);
+
+    result->flags = flags;
+
+    result->name = strdup(name);
+    result->name_hash = fnv_64a_hash(name);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : rule = règle de détection à consulter.                       *
+*                                                                             *
+*  Description : Indique les particularités liées à une règle de détection.   *
+*                                                                             *
+*  Retour      : Propriétés particulières attachées à la règle.               *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+ScanRuleFlags g_scan_rule_get_flags(const GScanRule *rule)
+{
+    ScanRuleFlags result;                   /* Fanions à retourner         */
+
+    result = rule->flags;
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : rule = règle de détection à consulter.                       *
 *                hash = empreinte précalculée associée au nom. [OUT]          *
 *                                                                             *
 *  Description : Indique le nom associé à une règle de détection.             *
@@ -202,7 +260,8 @@ const char *g_scan_rule_get_name(const GScanRule *rule, fnv64_t *hash)
 
     result = rule->name;
 
-    *hash = rule->name_hash;
+    if (hash != NULL)
+        *hash = rule->name_hash;
 
     return result;
 
@@ -635,7 +694,7 @@ char *g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context)
 *                padding  = éventuel bourrage initial à placer ou NULL.       *
 *                level    = profondeur actuelle.                              *
 *                fd       = canal d'écriture.                                 *
-*                trailing = impose une virgule finale ?                       *
+*                tail     = décline la pose d'une virgule finale ?            *
 *                                                                             *
 *  Description : Affiche une règle au format JSON.                            *
 *                                                                             *
@@ -645,10 +704,10 @@ char *g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context)
 *                                                                             *
 ******************************************************************************/
 
-void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool trailing)
+void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd, bool tail)
 {
     size_t i;                               /* Boucle de parcours          */
-    bool sub_trailing;                      /* Virgule finale              */
+    bool sub_tail;                          /* Saut de la virgule finale ? */
 
     /* Introduction */
 
@@ -677,9 +736,9 @@ void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, co
 
     for (i = 0; i < rule->bytes_used; i++)
     {
-        sub_trailing = ((i + 1) < rule->bytes_used);
+        sub_tail = ((i + 1) == rule->bytes_used);
 
-        g_search_pattern_output_to_json(rule->bytes_locals[i], context, padding, level + 2, fd, sub_trailing);
+        g_search_pattern_output_to_json(rule->bytes_locals[i], context, padding, level + 2, fd, sub_tail);
 
     }
 
@@ -707,10 +766,10 @@ void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, co
     for (i = 0; i < level; i++)
         write(fd, padding->data, padding->len);
 
-    if (trailing)
-        write(fd, "},\n", 3);
-    else
+    if (tail)
         write(fd, "}\n", 2);
+    else
+        write(fd, "},\n", 3);
 
 }
 
diff --git a/src/analysis/scan/rule.h b/src/analysis/scan/rule.h
index 6ab5105..3e6fe9d 100644
--- a/src/analysis/scan/rule.h
+++ b/src/analysis/scan/rule.h
@@ -53,11 +53,24 @@ typedef struct _GScanRule GScanRule;
 typedef struct _GScanRuleClass GScanRuleClass;
 
 
+/* Particularités de règle à faire valoir */
+typedef enum _ScanRuleFlags
+{
+    SRF_NONE    = (0 << 0),                 /* Absence de particularité    */
+    SRF_PRIVATE = (1 << 0),                 /* Règle silencieuse           */
+    SRF_GLOBAL  = (1 << 1)                  /* Règle de base implicite     */
+
+} ScanRuleFlags;
+
+
 /* Indique le type défini pour une règle de détection par motifs. */
 GType g_scan_rule_get_type(void);
 
 /* Crée une règle de détection statique à l'aide de motifs. */
-GScanRule *g_scan_rule_new(const char *);
+GScanRule *g_scan_rule_new(ScanRuleFlags, const char *);
+
+/* Indique les particularités liées à une règle de détection.   */
+ScanRuleFlags g_scan_rule_get_flags(const GScanRule *);
 
 /* Indique le nom associé à une règle de détection. */
 const char *g_scan_rule_get_name(const GScanRule *, fnv64_t *);
diff --git a/src/analysis/scan/scanner.c b/src/analysis/scan/scanner.c
index 41fa0de..7b553e6 100644
--- a/src/analysis/scan/scanner.c
+++ b/src/analysis/scan/scanner.c
@@ -364,7 +364,7 @@ bool g_content_scanner_include_resource(GContentScanner *scanner, const char *pa
 
             if (!result)
             {
-                inc_name = g_scan_rule_get_name(included->rules[i], (fnv64_t []) { 0 });
+                inc_name = g_scan_rule_get_name(included->rules[i], NULL);
 
                 log_variadic_message(LMT_ERROR, "Can not import from '%s': rule '%s' already exists!",
                                      path, inc_name);
@@ -459,7 +459,7 @@ bool g_content_scanner_add_rule(GContentScanner *scanner, GScanRule *rule)
 
     if (!result)
     {
-        inc_name = g_scan_rule_get_name(rule, (fnv64_t []) { 0 });
+        inc_name = g_scan_rule_get_name(rule, NULL);
 
         log_variadic_message(LMT_ERROR, "Can not add rule: '%s' already exists!", inc_name);
 
@@ -489,6 +489,11 @@ GScanContext *g_content_scanner_analyze(GContentScanner *scanner, GScanOptions *
     GScanContext *result;                   /* Bilan global à retourner    */
     bool status;                            /* Bilan d'opération locale    */
     size_t i;                               /* Boucle de parcours          */
+    bool global;                            /* Bilan des règles globales   */
+    GScanRule *rule;                        /* Règle à consulter           */
+    const char *name;                       /* Désignation de la règle     */
+
+    /* Préparations... */
 
     result = g_scan_context_new(options);
 
@@ -510,6 +515,8 @@ GScanContext *g_content_scanner_analyze(GContentScanner *scanner, GScanOptions *
 
     }
 
+    /* Phase d'analyse */
+
     g_scan_context_set_content(result, content);
 
     g_engine_backend_run_scan(scanner->data_backend, result);
@@ -519,6 +526,25 @@ GScanContext *g_content_scanner_analyze(GContentScanner *scanner, GScanOptions *
     for (i = 0; i < scanner->rule_count; i++)
         g_scan_rule_check(scanner->rules[i], scanner->data_backend, result);
 
+    /* Etablissement d'un bilan global */
+
+    global = true;
+
+    for (i = 0; i < scanner->rule_count; i++)
+    {
+        rule = scanner->rules[i];
+
+        if ((g_scan_rule_get_flags(rule) & SRF_GLOBAL) == 0)
+            continue;
+
+        name = g_scan_rule_get_name(rule, NULL);
+
+        global = g_scan_context_has_match_for_rule(result, name);
+
+    }
+
+    g_scan_context_set_global_match(result, global);
+
  exit:
 
     return result;
@@ -645,29 +671,45 @@ char *g_content_scanner_convert_to_text(const GContentScanner *scanner, GScanCon
 
 void g_content_scanner_output_to_json(const GContentScanner *scanner, GScanContext *context, const sized_string_t *padding, unsigned int level, int fd)
 {
-    size_t i;                               /* Boucle de parcours          */
-    bool trailing;                          /* Virgule finale              */
+    size_t k;                               /* Boucle de parcours #1       */
+    size_t i;                               /* Boucle de parcours #2       */
+    size_t last_displayed;                  /* Dernier indice affiché      */
+    bool tail;                              /* Saut de la virgule finale ? */
 
     /* Introduction */
 
-    for (i = 0; i < level; i++)
+    for (k = 0; k < level; k++)
         write(fd, padding->data, padding->len);
 
     write(fd, "[\n", 2);
 
     /* Sous-traitance aux règles */
 
+    for (i = scanner->rule_count; i > 0; i--)
+        if ((g_scan_rule_get_flags(scanner->rules[i - 1]) & SRF_PRIVATE) == 0)
+            break;
+
+    if (i == 0)
+        goto nothing_to_display;
+    else
+        last_displayed = i - 1;
+
     for (i = 0; i < scanner->rule_count; i++)
     {
-        trailing = ((i + 1) < scanner->rule_count);
+        if ((g_scan_rule_get_flags(scanner->rules[i]) & SRF_PRIVATE) == SRF_PRIVATE)
+            continue;
 
-        g_scan_rule_output_to_json(scanner->rules[i], context, padding, level + 1, fd, trailing);
+        tail = (i == last_displayed);
+
+        g_scan_rule_output_to_json(scanner->rules[i], context, padding, level + 1, fd, tail);
 
     }
 
     /* Conclusion */
 
-    for (i = 0; i < level; i++)
+ nothing_to_display:
+
+    for (k = 0; k < level; k++)
         write(fd, padding->data, padding->len);
 
     write(fd, "]\n", 2);
diff --git a/src/analysis/scan/tokens.l b/src/analysis/scan/tokens.l
index 241c973..1a17344 100644
--- a/src/analysis/scan/tokens.l
+++ b/src/analysis/scan/tokens.l
@@ -403,6 +403,9 @@ bytes_fuzzy_id [\*A-Za-z_][\*A-Za-z0-9_]*
 
 %{ /* Définition locale d'une règle */ %}
 
+                           "global" { return GLOBAL; }
+                          "private" { return PRIVATE; }
+
                              "rule" {
                                         PUSH_STATE(rule_intro);
                                         return RAW_RULE;
diff --git a/tests/analysis/scan/common.py b/tests/analysis/scan/common.py
index 3b52e38..507b7e2 100644
--- a/tests/analysis/scan/common.py
+++ b/tests/analysis/scan/common.py
@@ -33,6 +33,8 @@ class RostTestClass(ChrysalideTestCase):
         else:
             self.assertFalse(ctx.has_match_for_rule('test'))
 
+        return scanner, ctx
+
 
     def check_rule_success(self, rule, content = None):
         """Check for scan success."""
diff --git a/tests/analysis/scan/grammar.py b/tests/analysis/scan/grammar.py
index 8b18f81..13a255b 100644
--- a/tests/analysis/scan/grammar.py
+++ b/tests/analysis/scan/grammar.py
@@ -1,4 +1,6 @@
 
+import json
+
 from common import RostTestClass
 
 
@@ -181,7 +183,71 @@ rule test {
             self.check_rule_success(rule)
 
 
+    def testPrivateRules(self):
+        """Ensure private rules remain silent."""
+
+        for private in [ True, False ]:
+            for state in [ True, False ]:
+
+                rule = '''
+%srule silent {
+
+   condition:
+      %s
+
+}
+
+rule test {
+
+   condition:
+      silent
+
+}
+''' % ('private ' if private else '', 'true' if state else 'false')
+
+                scanner, ctx = self._validate_rule_result(rule, self._empty_content, state)
+
+                data = scanner.convert_to_json(ctx)
+                jdata = json.loads(data)
+
+                # Exemple :
+                #
+                # [{'bytes_patterns': [], 'matched': True, 'name': 'test'},
+                #  {'bytes_patterns': [], 'matched': True, 'name': 'silent'}]
+
+                found = len([ j['name'] for j in jdata if j['name'] == 'silent' ]) > 0
+
+                self.assertTrue(private ^ found)
+
+
+    def testGlobalRules(self):
+        """Take global rules into account."""
+
+        for glob_state in [ True, False ]:
+            for state in [ True, False ]:
+
+                rule = '''
+%srule silent {
+
+   condition:
+      %s
+
+}
+
+rule test {
+
+   condition:
+      true
+
+}
+''' % ('global ' if glob_state else '', 'true' if state else 'false')
+
+                expected = not(glob_state) or state
 
+                if expected:
+                    self.check_rule_success(rule)
+                else:
+                    self.check_rule_failure(rule)
 
 
 
-- 
cgit v0.11.2-87-g4458