• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 23374838686

21 Mar 2026 07:29AM UTC coverage: 59.341% (-20.0%) from 79.315%
23374838686

Pull #15075

github

web-flow
Merge 90b4e834f into 6587e363a
Pull Request #15075: Stack 8001 v16.4

38 of 70 new or added lines in 10 files covered. (54.29%)

34165 existing lines in 563 files now uncovered.

119621 of 201584 relevant lines covered (59.34%)

650666.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.16
/src/detect-engine-analyzer.c
1
/* Copyright (C) 2007-2025 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
/**
19
 * \file
20
 *
21
 * \author Eileen Donlon <emdonlo@gmail.com>
22
 * \author Victor Julien <victor@inliniac.net>
23
 *
24
 * Rule analyzers for the detection engine
25
 */
26

27
#include "suricata-common.h"
28
#include "suricata.h"
29
#include "rust.h"
30
#include "action-globals.h"
31
#include "detect.h"
32
#include "detect-parse.h"
33
#include "detect-engine.h"
34
#include "detect-engine-analyzer.h"
35
#include "detect-engine-mpm.h"
36
#include "detect-engine-uint.h"
37
#include "conf.h"
38
#include "detect-content.h"
39
#include "detect-pcre.h"
40
#include "detect-bytejump.h"
41
#include "detect-bytetest.h"
42
#include "detect-isdataat.h"
43
#include "detect-flow.h"
44
#include "detect-ttl.h"
45
#include "detect-tcp-flags.h"
46
#include "detect-tcp-ack.h"
47
#include "detect-ipopts.h"
48
#include "detect-tcp-seq.h"
49
#include "feature.h"
50
#include "util-print.h"
51
#include "util-time.h"
52
#include "util-validate.h"
53
#include "util-conf.h"
54
#include "detect-flowbits.h"
55
#include "detect-flowint.h"
56
#include "detect-xbits.h"
57
#include "util-var-name.h"
58
#include "detect-icmp-id.h"
59
#include "detect-tcp-window.h"
60

61
static int rule_warnings_only = 0;
62

63
/* Details for each buffer being tracked */
64
typedef struct DetectEngineAnalyzerItems {
65
    int16_t     item_id;
66
    bool        item_seen;
67
    bool        export_item_seen;
68
    bool        check_encoding_match;
69
    const char  *item_name;
70
    const char  *display_name;
71
} DetectEngineAnalyzerItems;
72

73
typedef struct FpPatternStats_ {
74
    uint16_t min;
75
    uint16_t max;
76
    uint32_t cnt;
77
    uint64_t tot;
78
} FpPatternStats;
79

80
/* Track which items require the item_seen value to be exposed */
81
struct ExposedItemSeen {
82
    const char  *bufname;
83
    bool        *item_seen_ptr;
84
};
85

86
typedef struct EngineAnalysisCtx_ {
87

88
    FILE *rule_engine_analysis_fp;
89
    FILE *fp_engine_analysis_fp;
90

91
    DetectEngineAnalyzerItems *analyzer_items;
92
    char *file_prefix;
93
    pcre2_code *percent_re;
94

95
    /*
96
     * This array contains the map between the `analyzer_items` array listed above and
97
     * the item ids returned by DetectBufferTypeGetByName. Iterating signature's sigmatch
98
     * array provides list_ids. The map converts those ids into elements of the
99
     * analyzer items array.
100
     *
101
     * Ultimately, the g_buffer_type_hash is searched for each buffer name. The size of that
102
     * hashlist is 256, so that's the value we use here.
103
     */
104
    int16_t analyzer_item_map[256];
105
    FpPatternStats fp_pattern_stats[DETECT_SM_LIST_MAX];
106
    /*
107
     * Certain values must be directly accessible. This array contains items that are directly
108
     * accessed when checking if they've been seen or not.
109
     */
110
    struct ExposedItemSeen exposed_item_seen_list[2];
111

112
    bool analyzer_initialized;
113
} EngineAnalysisCtx;
114

115
const DetectEngineAnalyzerItems analyzer_items[] = {
116
    /* request keywords */
117
    { 0, false, false, true, "http_uri", "http uri" },
118
    { 0, false, false, false, "http_raw_uri", "http raw uri" },
119
    { 0, false, true, false, "http_method", "http method" },
120
    { 0, false, false, false, "http_request_line", "http request line" },
121
    { 0, false, false, false, "http_client_body", "http client body" },
122
    { 0, false, false, true, "http_header", "http header" },
123
    { 0, false, false, false, "http_raw_header", "http raw header" },
124
    { 0, false, false, true, "http_cookie", "http cookie" },
125
    { 0, false, false, false, "http_user_agent", "http user agent" },
126
    { 0, false, false, false, "http_host", "http host" },
127
    { 0, false, false, false, "http_raw_host", "http raw host" },
128
    { 0, false, false, false, "http_accept_enc", "http accept enc" },
129
    { 0, false, false, false, "http_referer", "http referer" },
130
    { 0, false, false, false, "http_content_type", "http content type" },
131
    { 0, false, false, false, "http_header_names", "http header names" },
132

133
    /* response keywords not listed above */
134
    { 0, false, false, false, "http_stat_msg", "http stat msg" },
135
    { 0, false, false, false, "http_stat_code", "http stat code" },
136
    { 0, false, true, false, "file_data", "http server body" },
137

138
    /* missing request keywords */
139
    { 0, false, false, false, "http_request_line", "http request line" },
140
    { 0, false, false, false, "http_accept", "http accept" },
141
    { 0, false, false, false, "http_accept_lang", "http accept lang" },
142
    { 0, false, false, false, "http_connection", "http connection" },
143
    { 0, false, false, false, "http_content_len", "http content len" },
144
    { 0, false, false, false, "http_protocol", "http protocol" },
145
    { 0, false, false, false, "http_start", "http start" },
146

147
    /* missing response keywords; some of the missing are listed above*/
148
    { 0, false, false, false, "http_response_line", "http response line" },
149
    { 0, false, false, false, "http.server", "http server" },
150
    { 0, false, false, false, "http.location", "http location" },
151
};
152

153
static void FpPatternStatsAdd(FpPatternStats *fp, int list, uint16_t patlen)
UNCOV
154
{
×
UNCOV
155
    if (list < 0 || list >= DETECT_SM_LIST_MAX)
×
UNCOV
156
        return;
×
157

UNCOV
158
    FpPatternStats *f = &fp[list];
×
159

UNCOV
160
    if (f->min == 0)
×
UNCOV
161
        f->min = patlen;
×
UNCOV
162
    else if (patlen < f->min)
×
UNCOV
163
        f->min = patlen;
×
164

UNCOV
165
    if (patlen > f->max)
×
UNCOV
166
        f->max = patlen;
×
167

UNCOV
168
    f->cnt++;
×
UNCOV
169
    f->tot += patlen;
×
UNCOV
170
}
×
171

172
void EngineAnalysisFP(const DetectEngineCtx *de_ctx, const Signature *s, const char *line)
UNCOV
173
{
×
UNCOV
174
    int fast_pattern_set = 0;
×
UNCOV
175
    int fast_pattern_only_set = 0;
×
UNCOV
176
    int fast_pattern_chop_set = 0;
×
UNCOV
177
    const DetectContentData *fp_cd = NULL;
×
UNCOV
178
    const SigMatch *mpm_sm = s->init_data->mpm_sm;
×
UNCOV
179
    const int mpm_sm_list = s->init_data->mpm_sm_list;
×
180

UNCOV
181
    if (mpm_sm != NULL) {
×
UNCOV
182
        fp_cd = (DetectContentData *)mpm_sm->ctx;
×
UNCOV
183
        if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN) {
×
UNCOV
184
            fast_pattern_set = 1;
×
UNCOV
185
            if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN_ONLY) {
×
UNCOV
186
                fast_pattern_only_set = 1;
×
UNCOV
187
            } else if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN_CHOP) {
×
188
                fast_pattern_chop_set = 1;
×
189
            }
×
UNCOV
190
        }
×
UNCOV
191
    }
×
192

UNCOV
193
    FILE *fp = de_ctx->ea->rule_engine_analysis_fp;
×
UNCOV
194
    fprintf(fp, "== Sid: %u ==\n", s->id);
×
UNCOV
195
    fprintf(fp, "%s\n", line);
×
196

UNCOV
197
    fprintf(fp, "    Fast Pattern analysis:\n");
×
UNCOV
198
    if (s->init_data->prefilter_sm != NULL) {
×
UNCOV
199
        fprintf(fp, "        Prefilter on: %s\n",
×
UNCOV
200
                sigmatch_table[s->init_data->prefilter_sm->type].name);
×
UNCOV
201
        fprintf(fp, "\n");
×
UNCOV
202
        return;
×
UNCOV
203
    }
×
204

UNCOV
205
    if (fp_cd == NULL) {
×
UNCOV
206
        fprintf(fp, "        No content present\n");
×
UNCOV
207
        fprintf(fp, "\n");
×
UNCOV
208
        return;
×
UNCOV
209
    }
×
210

UNCOV
211
    fprintf(fp, "        Fast pattern matcher: ");
×
UNCOV
212
    int list_type = mpm_sm_list;
×
UNCOV
213
    if (list_type == DETECT_SM_LIST_PMATCH)
×
UNCOV
214
        fprintf(fp, "content\n");
×
UNCOV
215
    else {
×
UNCOV
216
        const char *desc = DetectEngineBufferTypeGetDescriptionById(de_ctx, list_type);
×
UNCOV
217
        const char *name = DetectEngineBufferTypeGetNameById(de_ctx, list_type);
×
UNCOV
218
        if (desc && name) {
×
UNCOV
219
            fprintf(fp, "%s (%s)\n", desc, name);
×
UNCOV
220
        }
×
UNCOV
221
    }
×
222

UNCOV
223
    int flags_set = 0;
×
UNCOV
224
    fprintf(fp, "        Flags:");
×
UNCOV
225
    if (fp_cd->flags & DETECT_CONTENT_OFFSET) {
×
UNCOV
226
        fprintf(fp, " Offset");
×
UNCOV
227
        flags_set = 1;
×
UNCOV
228
    } if (fp_cd->flags & DETECT_CONTENT_DEPTH) {
×
UNCOV
229
        fprintf(fp, " Depth");
×
UNCOV
230
        flags_set = 1;
×
UNCOV
231
    }
×
UNCOV
232
    if (fp_cd->flags & DETECT_CONTENT_WITHIN) {
×
UNCOV
233
        fprintf(fp, " Within");
×
UNCOV
234
        flags_set = 1;
×
UNCOV
235
    }
×
UNCOV
236
    if (fp_cd->flags & DETECT_CONTENT_DISTANCE) {
×
UNCOV
237
        fprintf(fp, " Distance");
×
UNCOV
238
        flags_set = 1;
×
UNCOV
239
    }
×
UNCOV
240
    if (fp_cd->flags & DETECT_CONTENT_NOCASE) {
×
UNCOV
241
        fprintf(fp, " Nocase");
×
UNCOV
242
        flags_set = 1;
×
UNCOV
243
    }
×
UNCOV
244
    if (fp_cd->flags & DETECT_CONTENT_NEGATED) {
×
UNCOV
245
        fprintf(fp, " Negated");
×
UNCOV
246
        flags_set = 1;
×
UNCOV
247
    }
×
UNCOV
248
    if (flags_set == 0)
×
UNCOV
249
        fprintf(fp, " None");
×
UNCOV
250
    fprintf(fp, "\n");
×
251

UNCOV
252
    fprintf(fp, "        Fast pattern set: %s\n", fast_pattern_set ? "yes" : "no");
×
UNCOV
253
    fprintf(fp, "        Fast pattern only set: %s\n", fast_pattern_only_set ? "yes" : "no");
×
UNCOV
254
    fprintf(fp, "        Fast pattern chop set: %s\n", fast_pattern_chop_set ? "yes" : "no");
×
UNCOV
255
    if (fast_pattern_chop_set) {
×
256
        fprintf(fp, "        Fast pattern offset, length: %u, %u\n", fp_cd->fp_chop_offset,
×
257
                fp_cd->fp_chop_len);
×
258
    }
×
259

UNCOV
260
    uint16_t patlen = fp_cd->content_len;
×
UNCOV
261
    uint8_t *pat = SCMalloc(fp_cd->content_len + 1);
×
UNCOV
262
    if (unlikely(pat == NULL)) {
×
263
        FatalError("Error allocating memory");
×
264
    }
×
UNCOV
265
    memcpy(pat, fp_cd->content, fp_cd->content_len);
×
UNCOV
266
    pat[fp_cd->content_len] = '\0';
×
UNCOV
267
    fprintf(fp, "        Original content: ");
×
UNCOV
268
    PrintRawUriFp(fp, pat, patlen);
×
UNCOV
269
    fprintf(fp, "\n");
×
270

UNCOV
271
    if (fast_pattern_chop_set) {
×
272
        SCFree(pat);
×
273
        patlen = fp_cd->fp_chop_len;
×
274
        pat = SCMalloc(fp_cd->fp_chop_len + 1);
×
275
        if (unlikely(pat == NULL)) {
×
276
            exit(EXIT_FAILURE);
×
277
        }
×
278
        memcpy(pat, fp_cd->content + fp_cd->fp_chop_offset, fp_cd->fp_chop_len);
×
279
        pat[fp_cd->fp_chop_len] = '\0';
×
280
        fprintf(fp, "        Final content: ");
×
281
        PrintRawUriFp(fp, pat, patlen);
×
282
        fprintf(fp, "\n");
×
283

284
        FpPatternStatsAdd(&de_ctx->ea->fp_pattern_stats[0], list_type, patlen);
×
UNCOV
285
    } else {
×
UNCOV
286
        fprintf(fp, "        Final content: ");
×
UNCOV
287
        PrintRawUriFp(fp, pat, patlen);
×
UNCOV
288
        fprintf(fp, "\n");
×
289

UNCOV
290
        FpPatternStatsAdd(&de_ctx->ea->fp_pattern_stats[0], list_type, patlen);
×
UNCOV
291
    }
×
UNCOV
292
    SCFree(pat);
×
293

UNCOV
294
    fprintf(fp, "\n");
×
UNCOV
295
}
×
296

297
/**
298
 * \brief Sets up the fast pattern analyzer according to the config.
299
 *
300
 * \retval 1 If rule analyzer successfully enabled.
301
 * \retval 0 If not enabled.
302
 */
303
static int SetupFPAnalyzer(DetectEngineCtx *de_ctx)
UNCOV
304
{
×
UNCOV
305
    int fp_engine_analysis_set = 0;
×
306

UNCOV
307
    if ((SCConfGetBool("engine-analysis.rules-fast-pattern", &fp_engine_analysis_set)) == 0) {
×
UNCOV
308
        return false;
×
UNCOV
309
    }
×
310

UNCOV
311
    if (fp_engine_analysis_set == 0)
×
312
        return false;
×
313

UNCOV
314
    const char *log_dir = SCConfigGetLogDirectory();
×
UNCOV
315
    char *log_path = SCMalloc(PATH_MAX);
×
UNCOV
316
    if (log_path == NULL) {
×
317
        FatalError("Unable to allocate scratch memory for rule filename");
×
318
    }
×
UNCOV
319
    snprintf(log_path, PATH_MAX, "%s/%s%s", log_dir,
×
UNCOV
320
            de_ctx->ea->file_prefix ? de_ctx->ea->file_prefix : "", "rules_fast_pattern.txt");
×
321

UNCOV
322
    FILE *fp = fopen(log_path, "w");
×
UNCOV
323
    if (fp == NULL) {
×
324
        SCLogError("failed to open %s: %s", log_path, strerror(errno));
×
325
        SCFree(log_path);
×
326
        return false;
×
327
    }
×
328

UNCOV
329
    de_ctx->ea->fp_engine_analysis_fp = fp;
×
330

UNCOV
331
    SCLogInfo("Engine-Analysis for fast_pattern printed to file - %s",
×
UNCOV
332
              log_path);
×
UNCOV
333
    SCFree(log_path);
×
334

UNCOV
335
    struct timeval tval;
×
UNCOV
336
    gettimeofday(&tval, NULL);
×
UNCOV
337
    struct tm local_tm;
×
UNCOV
338
    struct tm *tms = SCLocalTime(tval.tv_sec, &local_tm);
×
UNCOV
339
    fprintf(fp, "----------------------------------------------"
×
UNCOV
340
                "---------------------\n");
×
UNCOV
341
    fprintf(fp,
×
UNCOV
342
            "Date: %" PRId32 "/%" PRId32 "/%04d -- "
×
UNCOV
343
            "%02d:%02d:%02d\n",
×
UNCOV
344
            tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min,
×
UNCOV
345
            tms->tm_sec);
×
UNCOV
346
    fprintf(fp, "----------------------------------------------"
×
UNCOV
347
                "---------------------\n");
×
348

UNCOV
349
    memset(&de_ctx->ea->fp_pattern_stats[0], 0, sizeof(de_ctx->ea->fp_pattern_stats));
×
UNCOV
350
    return true;
×
UNCOV
351
}
×
352

353
/**
354
 * \brief Compiles regex for rule analysis
355
 * \retval 1 if successful
356
 * \retval 0 if on error
357
 */
358
static bool PerCentEncodingSetup(EngineAnalysisCtx *ea_ctx)
UNCOV
359
{
×
UNCOV
360
#define DETECT_PERCENT_ENCODING_REGEX "%[0-9|a-f|A-F]{2}"
×
UNCOV
361
    int en;
×
UNCOV
362
    PCRE2_SIZE eo = 0;
×
UNCOV
363
    int opts = 0; // PCRE2_NEWLINE_ANY??
×
364

UNCOV
365
    ea_ctx->percent_re = pcre2_compile((PCRE2_SPTR8)DETECT_PERCENT_ENCODING_REGEX,
×
UNCOV
366
            PCRE2_ZERO_TERMINATED, opts, &en, &eo, NULL);
×
UNCOV
367
    if (ea_ctx->percent_re == NULL) {
×
368
        PCRE2_UCHAR errbuffer[256];
×
369
        pcre2_get_error_message(en, errbuffer, sizeof(errbuffer));
×
370
        SCLogError("Compile of \"%s\" failed at offset %d: %s", DETECT_PERCENT_ENCODING_REGEX,
×
371
                (int)eo, errbuffer);
×
372
        return false;
×
373
    }
×
374

UNCOV
375
    return true;
×
UNCOV
376
}
×
377
/**
378
 * \brief Sets up the rule analyzer according to the config
379
 * \retval 1 if rule analyzer successfully enabled
380
 * \retval 0 if not enabled
381
 */
382
static int SetupRuleAnalyzer(DetectEngineCtx *de_ctx)
UNCOV
383
{
×
UNCOV
384
    SCConfNode *conf = SCConfGetNode("engine-analysis");
×
UNCOV
385
    int enabled = 0;
×
UNCOV
386
    if (conf != NULL) {
×
UNCOV
387
        const char *value = SCConfNodeLookupChildValue(conf, "rules");
×
UNCOV
388
        if (value && SCConfValIsTrue(value)) {
×
UNCOV
389
            enabled = 1;
×
UNCOV
390
        } else if (value && strcasecmp(value, "warnings-only") == 0) {
×
391
            enabled = 1;
×
392
            rule_warnings_only = 1;
×
393
        }
×
UNCOV
394
        if (enabled) {
×
UNCOV
395
            const char *log_dir;
×
UNCOV
396
            log_dir = SCConfigGetLogDirectory();
×
UNCOV
397
            char log_path[PATH_MAX];
×
UNCOV
398
            snprintf(log_path, sizeof(log_path), "%s/%s%s", log_dir,
×
UNCOV
399
                    de_ctx->ea->file_prefix ? de_ctx->ea->file_prefix : "", "rules_analysis.txt");
×
UNCOV
400
            de_ctx->ea->rule_engine_analysis_fp = fopen(log_path, "w");
×
UNCOV
401
            if (de_ctx->ea->rule_engine_analysis_fp == NULL) {
×
402
                SCLogError("failed to open %s: %s", log_path, strerror(errno));
×
403
                return 0;
×
404
            }
×
405

UNCOV
406
            SCLogInfo("Engine-Analysis for rules printed to file - %s",
×
UNCOV
407
                      log_path);
×
408

UNCOV
409
            struct timeval tval;
×
UNCOV
410
            gettimeofday(&tval, NULL);
×
UNCOV
411
            struct tm local_tm;
×
UNCOV
412
            struct tm *tms = SCLocalTime(tval.tv_sec, &local_tm);
×
UNCOV
413
            fprintf(de_ctx->ea->rule_engine_analysis_fp,
×
UNCOV
414
                    "----------------------------------------------"
×
UNCOV
415
                    "---------------------\n");
×
UNCOV
416
            fprintf(de_ctx->ea->rule_engine_analysis_fp,
×
UNCOV
417
                    "Date: %" PRId32 "/%" PRId32 "/%04d -- "
×
UNCOV
418
                    "%02d:%02d:%02d\n",
×
UNCOV
419
                    tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min,
×
UNCOV
420
                    tms->tm_sec);
×
UNCOV
421
            fprintf(de_ctx->ea->rule_engine_analysis_fp,
×
UNCOV
422
                    "----------------------------------------------"
×
UNCOV
423
                    "---------------------\n");
×
424

425
            /*compile regex's for rule analysis*/
UNCOV
426
            if (!PerCentEncodingSetup(de_ctx->ea)) {
×
427
                fprintf(de_ctx->ea->rule_engine_analysis_fp,
×
428
                        "Error compiling regex; can't check for percent encoding in normalized "
×
429
                        "http content.\n");
×
430
            }
×
UNCOV
431
        }
×
UNCOV
432
    }
×
433
    else {
×
434
        SCLogInfo("Conf parameter \"engine-analysis.rules\" not found. "
×
435
                                      "Defaulting to not printing the rules analysis report.");
×
436
    }
×
UNCOV
437
    if (!enabled) {
×
438
        SCLogInfo("Engine-Analysis for rules disabled in conf file.");
×
439
        return 0;
×
440
    }
×
UNCOV
441
    return 1;
×
UNCOV
442
}
×
443

444
static void CleanupFPAnalyzer(DetectEngineCtx *de_ctx)
UNCOV
445
{
×
UNCOV
446
    FILE *fp = de_ctx->ea->rule_engine_analysis_fp;
×
UNCOV
447
    fprintf(fp, "============\n"
×
UNCOV
448
                "Summary:\n============\n");
×
449

UNCOV
450
    for (int i = 0; i < DETECT_SM_LIST_MAX; i++) {
×
UNCOV
451
        FpPatternStats *f = &de_ctx->ea->fp_pattern_stats[i];
×
UNCOV
452
        if (f->cnt == 0)
×
UNCOV
453
            continue;
×
454

UNCOV
455
        fprintf(fp,
×
UNCOV
456
                "%s, smallest pattern %u byte(s), longest pattern %u byte(s), number of patterns "
×
UNCOV
457
                "%u, avg pattern len %.2f byte(s)\n",
×
UNCOV
458
                DetectSigmatchListEnumToString(i), f->min, f->max, f->cnt,
×
UNCOV
459
                (float)((double)f->tot / (float)f->cnt));
×
UNCOV
460
    }
×
461

UNCOV
462
    fclose(de_ctx->ea->rule_engine_analysis_fp);
×
UNCOV
463
    de_ctx->ea->rule_engine_analysis_fp = NULL;
×
UNCOV
464
}
×
465

466
static void CleanupRuleAnalyzer(DetectEngineCtx *de_ctx)
UNCOV
467
{
×
UNCOV
468
    if (de_ctx->ea->fp_engine_analysis_fp != NULL) {
×
UNCOV
469
        fclose(de_ctx->ea->fp_engine_analysis_fp);
×
UNCOV
470
        de_ctx->ea->fp_engine_analysis_fp = NULL;
×
UNCOV
471
    }
×
UNCOV
472
    if (de_ctx->ea->percent_re != NULL) {
×
UNCOV
473
        pcre2_code_free(de_ctx->ea->percent_re);
×
UNCOV
474
    }
×
UNCOV
475
}
×
476

477
void SetupEngineAnalysis(DetectEngineCtx *de_ctx, bool *fp_analysis, bool *rule_analysis)
UNCOV
478
{
×
UNCOV
479
    *fp_analysis = false;
×
UNCOV
480
    *rule_analysis = false;
×
481

UNCOV
482
    EngineAnalysisCtx *ea = SCCalloc(1, sizeof(EngineAnalysisCtx));
×
UNCOV
483
    if (ea == NULL) {
×
484
        FatalError("Unable to allocate per-engine analysis context");
×
485
    }
×
486

UNCOV
487
    ea->file_prefix = NULL;
×
UNCOV
488
    size_t cfg_prefix_len = strlen(de_ctx->config_prefix);
×
UNCOV
489
    if (cfg_prefix_len > 0) {
×
UNCOV
490
        char prefix[sizeof(de_ctx->config_prefix) + 1];
×
UNCOV
491
        snprintf(prefix, sizeof(prefix), "%s.", de_ctx->config_prefix);
×
UNCOV
492
        ea->file_prefix = SCStrdup(prefix);
×
UNCOV
493
        if (ea->file_prefix == NULL) {
×
494
            FatalError("Unable to allocate per-engine analysis context name buffer");
×
495
        }
×
UNCOV
496
    }
×
497

UNCOV
498
    de_ctx->ea = ea;
×
499

UNCOV
500
    *fp_analysis = SetupFPAnalyzer(de_ctx);
×
UNCOV
501
    *rule_analysis = SetupRuleAnalyzer(de_ctx);
×
502

UNCOV
503
    if (!(*fp_analysis || *rule_analysis)) {
×
504
        if (ea->file_prefix)
×
505
            SCFree(ea->file_prefix);
×
506
        if (ea->analyzer_items)
×
507
            SCFree(ea->analyzer_items);
×
508
        SCFree(ea);
×
509
    }
×
UNCOV
510
}
×
511

512
void CleanupEngineAnalysis(DetectEngineCtx *de_ctx)
UNCOV
513
{
×
UNCOV
514
    if (de_ctx->ea) {
×
UNCOV
515
        CleanupRuleAnalyzer(de_ctx);
×
UNCOV
516
        CleanupFPAnalyzer(de_ctx);
×
UNCOV
517
        if (de_ctx->ea->file_prefix)
×
UNCOV
518
            SCFree(de_ctx->ea->file_prefix);
×
UNCOV
519
        if (de_ctx->ea->analyzer_items)
×
UNCOV
520
            SCFree(de_ctx->ea->analyzer_items);
×
UNCOV
521
        SCFree(de_ctx->ea);
×
UNCOV
522
        de_ctx->ea = NULL;
×
UNCOV
523
    }
×
UNCOV
524
}
×
525

526
/**
527
 * \brief Checks for % encoding in content.
528
 * \param Pointer to content
529
 * \retval number of matches if content has % encoding
530
 * \retval 0 if it doesn't have % encoding
531
 * \retval -1 on error
532
 */
533
static int PerCentEncodingMatch(EngineAnalysisCtx *ea_ctx, uint8_t *content, uint16_t content_len)
534
{
×
535
    int ret = 0;
×
536

537
    pcre2_match_data *match = pcre2_match_data_create_from_pattern(ea_ctx->percent_re, NULL);
×
538
    ret = pcre2_match(ea_ctx->percent_re, (PCRE2_SPTR8)content, content_len, 0, 0, match, NULL);
×
539
    if (ret == -1) {
×
540
        return 0;
×
541
    } else if (ret < -1) {
×
542
        SCLogError("Error parsing content - %s; error code is %d", content, ret);
×
543
        ret = -1;
×
544
    }
×
545
    pcre2_match_data_free(match);
×
546
    return ret;
×
547
}
×
548

549
static void EngineAnalysisRulesPrintFP(const DetectEngineCtx *de_ctx, const Signature *s)
UNCOV
550
{
×
UNCOV
551
    const DetectContentData *fp_cd = NULL;
×
UNCOV
552
    const SigMatch *mpm_sm = s->init_data->mpm_sm;
×
UNCOV
553
    const int mpm_sm_list = s->init_data->mpm_sm_list;
×
554

UNCOV
555
    if (mpm_sm != NULL) {
×
UNCOV
556
        fp_cd = (DetectContentData *)mpm_sm->ctx;
×
UNCOV
557
    }
×
558

UNCOV
559
    if (fp_cd == NULL) {
×
UNCOV
560
        return;
×
UNCOV
561
    }
×
562

UNCOV
563
    uint16_t patlen = fp_cd->content_len;
×
UNCOV
564
    uint8_t *pat = SCMalloc(fp_cd->content_len + 1);
×
UNCOV
565
    if (unlikely(pat == NULL)) {
×
566
        FatalError("Error allocating memory");
×
567
    }
×
568

UNCOV
569
    EngineAnalysisCtx *ea_ctx = de_ctx->ea;
×
570

UNCOV
571
    memcpy(pat, fp_cd->content, fp_cd->content_len);
×
UNCOV
572
    pat[fp_cd->content_len] = '\0';
×
573

UNCOV
574
    if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN_CHOP) {
×
575
        SCFree(pat);
×
576
        patlen = fp_cd->fp_chop_len;
×
577
        pat = SCMalloc(fp_cd->fp_chop_len + 1);
×
578
        if (unlikely(pat == NULL)) {
×
579
            exit(EXIT_FAILURE);
×
580
        }
×
581
        memcpy(pat, fp_cd->content + fp_cd->fp_chop_offset, fp_cd->fp_chop_len);
×
582
        pat[fp_cd->fp_chop_len] = '\0';
×
583
        fprintf(ea_ctx->rule_engine_analysis_fp, "    Fast Pattern \"");
×
584
        PrintRawUriFp(ea_ctx->rule_engine_analysis_fp, pat, patlen);
×
UNCOV
585
    } else {
×
UNCOV
586
        fprintf(ea_ctx->rule_engine_analysis_fp, "    Fast Pattern \"");
×
UNCOV
587
        PrintRawUriFp(ea_ctx->rule_engine_analysis_fp, pat, patlen);
×
UNCOV
588
    }
×
UNCOV
589
    SCFree(pat);
×
590

UNCOV
591
    fprintf(ea_ctx->rule_engine_analysis_fp, "\" on \"");
×
592

UNCOV
593
    const int list_type = mpm_sm_list;
×
UNCOV
594
    if (list_type == DETECT_SM_LIST_PMATCH) {
×
UNCOV
595
        int payload = 0;
×
UNCOV
596
        int stream = 0;
×
UNCOV
597
        if (SignatureHasPacketContent(s))
×
UNCOV
598
            payload = 1;
×
UNCOV
599
        if (SignatureHasStreamContent(s))
×
UNCOV
600
            stream = 1;
×
UNCOV
601
        fprintf(ea_ctx->rule_engine_analysis_fp, "%s",
×
UNCOV
602
                payload ? (stream ? "payload and reassembled stream" : "payload")
×
UNCOV
603
                        : "reassembled stream");
×
UNCOV
604
    }
×
UNCOV
605
    else {
×
UNCOV
606
        const char *desc = DetectEngineBufferTypeGetDescriptionById(de_ctx, list_type);
×
UNCOV
607
        const char *name = DetectEngineBufferTypeGetNameById(de_ctx, list_type);
×
UNCOV
608
        if (desc && name) {
×
UNCOV
609
            fprintf(ea_ctx->rule_engine_analysis_fp, "%s (%s)", desc, name);
×
UNCOV
610
        } else if (desc || name) {
×
611
            fprintf(ea_ctx->rule_engine_analysis_fp, "%s", desc ? desc : name);
×
612
        }
×
613

UNCOV
614
    }
×
615

UNCOV
616
    fprintf(ea_ctx->rule_engine_analysis_fp, "\" ");
×
UNCOV
617
    const DetectBufferType *bt = DetectEngineBufferTypeGetById(de_ctx, list_type);
×
UNCOV
618
    if (bt && bt->transforms.cnt) {
×
UNCOV
619
        fprintf(ea_ctx->rule_engine_analysis_fp, "(with %d transform(s)) ", bt->transforms.cnt);
×
UNCOV
620
    }
×
UNCOV
621
    fprintf(ea_ctx->rule_engine_analysis_fp, "buffer.\n");
×
UNCOV
622
}
×
623

624
void EngineAnalysisRulesFailure(
625
        const DetectEngineCtx *de_ctx, const char *line, const char *file, int lineno)
UNCOV
626
{
×
UNCOV
627
    FILE *tmp_fp = de_ctx->ea->fp_engine_analysis_fp ? de_ctx->ea->fp_engine_analysis_fp
×
UNCOV
628
                                                     : de_ctx->ea->rule_engine_analysis_fp;
×
UNCOV
629
    if (tmp_fp) {
×
UNCOV
630
        fprintf(tmp_fp, "== Sid: UNKNOWN ==\n");
×
UNCOV
631
        fprintf(tmp_fp, "%s\n", line);
×
UNCOV
632
        fprintf(tmp_fp, "    FAILURE: invalid rule.\n");
×
UNCOV
633
        fprintf(tmp_fp, "    File: %s.\n", file);
×
UNCOV
634
        fprintf(tmp_fp, "    Line: %d.\n", lineno);
×
UNCOV
635
        fprintf(tmp_fp, "\n");
×
UNCOV
636
    }
×
UNCOV
637
}
×
638

639
typedef struct RuleAnalyzer {
640
    SCJsonBuilder *js; /* document root */
641

642
    SCJsonBuilder *js_warnings;
643
    SCJsonBuilder *js_notes;
644
} RuleAnalyzer;
645

646
static void ATTR_FMT_PRINTF(2, 3) AnalyzerNote(RuleAnalyzer *ctx, char *fmt, ...)
UNCOV
647
{
×
UNCOV
648
    va_list ap;
×
UNCOV
649
    char str[1024];
×
650

UNCOV
651
    va_start(ap, fmt);
×
UNCOV
652
    vsnprintf(str, sizeof(str), fmt, ap);
×
UNCOV
653
    va_end(ap);
×
654

UNCOV
655
    if (!ctx->js_notes)
×
UNCOV
656
        ctx->js_notes = SCJbNewArray();
×
UNCOV
657
    if (ctx->js_notes)
×
UNCOV
658
        SCJbAppendString(ctx->js_notes, str);
×
UNCOV
659
}
×
660

661
static void ATTR_FMT_PRINTF(2, 3) AnalyzerWarning(RuleAnalyzer *ctx, char *fmt, ...)
UNCOV
662
{
×
UNCOV
663
    va_list ap;
×
UNCOV
664
    char str[1024];
×
665

UNCOV
666
    va_start(ap, fmt);
×
UNCOV
667
    vsnprintf(str, sizeof(str), fmt, ap);
×
UNCOV
668
    va_end(ap);
×
669

UNCOV
670
    if (!ctx->js_warnings)
×
UNCOV
671
        ctx->js_warnings = SCJbNewArray();
×
UNCOV
672
    if (ctx->js_warnings)
×
UNCOV
673
        SCJbAppendString(ctx->js_warnings, str);
×
UNCOV
674
}
×
675

UNCOV
676
#define CHECK(pat) if (strlen((pat)) <= len && memcmp((pat), buf, MIN(len, strlen((pat)))) == 0) return true;
×
677

678
static bool LooksLikeHTTPMethod(const uint8_t *buf, uint16_t len)
UNCOV
679
{
×
UNCOV
680
    CHECK("GET /");
×
UNCOV
681
    CHECK("POST /");
×
UNCOV
682
    CHECK("HEAD /");
×
UNCOV
683
    CHECK("PUT /");
×
UNCOV
684
    return false;
×
UNCOV
685
}
×
686

687
static bool LooksLikeHTTPUA(const uint8_t *buf, uint16_t len)
UNCOV
688
{
×
UNCOV
689
    CHECK("User-Agent: ");
×
UNCOV
690
    CHECK("\nUser-Agent: ");
×
UNCOV
691
    return false;
×
UNCOV
692
}
×
693

694
static void DumpContent(SCJsonBuilder *js, const DetectContentData *cd)
UNCOV
695
{
×
UNCOV
696
    char pattern_str[1024] = "";
×
UNCOV
697
    DetectContentPatternPrettyPrint(cd->content, cd->content_len, pattern_str, sizeof(pattern_str));
×
698

UNCOV
699
    SCJbSetString(js, "pattern", pattern_str);
×
UNCOV
700
    SCJbSetUint(js, "length", cd->content_len);
×
UNCOV
701
    SCJbSetBool(js, "nocase", cd->flags & DETECT_CONTENT_NOCASE);
×
UNCOV
702
    SCJbSetBool(js, "negated", cd->flags & DETECT_CONTENT_NEGATED);
×
UNCOV
703
    SCJbSetBool(js, "starts_with", cd->flags & DETECT_CONTENT_STARTS_WITH);
×
UNCOV
704
    SCJbSetBool(js, "ends_with", cd->flags & DETECT_CONTENT_ENDS_WITH);
×
UNCOV
705
    SCJbSetBool(js, "is_mpm", cd->flags & DETECT_CONTENT_MPM);
×
UNCOV
706
    SCJbSetBool(js, "no_double_inspect", cd->flags & DETECT_CONTENT_NO_DOUBLE_INSPECTION_REQUIRED);
×
UNCOV
707
    if (cd->flags & DETECT_CONTENT_OFFSET) {
×
UNCOV
708
        SCJbSetUint(js, "offset", cd->offset);
×
UNCOV
709
    }
×
UNCOV
710
    if (cd->flags & DETECT_CONTENT_DEPTH) {
×
UNCOV
711
        SCJbSetUint(js, "depth", cd->depth);
×
UNCOV
712
    }
×
UNCOV
713
    if (cd->flags & DETECT_CONTENT_DISTANCE) {
×
UNCOV
714
        SCJbSetInt(js, "distance", cd->distance);
×
UNCOV
715
    }
×
UNCOV
716
    if (cd->flags & DETECT_CONTENT_WITHIN) {
×
UNCOV
717
        SCJbSetInt(js, "within", cd->within);
×
UNCOV
718
    }
×
UNCOV
719
    SCJbSetBool(js, "fast_pattern", cd->flags & DETECT_CONTENT_FAST_PATTERN);
×
UNCOV
720
    SCJbSetBool(js, "relative_next", cd->flags & DETECT_CONTENT_RELATIVE_NEXT);
×
UNCOV
721
}
×
722

723
static void DumpPcre(SCJsonBuilder *js, const DetectPcreData *cd)
UNCOV
724
{
×
UNCOV
725
    SCJbSetBool(js, "relative", cd->flags & DETECT_PCRE_RELATIVE);
×
UNCOV
726
    SCJbSetBool(js, "relative_next", cd->flags & DETECT_PCRE_RELATIVE_NEXT);
×
UNCOV
727
    SCJbSetBool(js, "nocase", cd->flags & DETECT_PCRE_CASELESS);
×
UNCOV
728
    SCJbSetBool(js, "negated", cd->flags & DETECT_PCRE_NEGATE);
×
UNCOV
729
}
×
730

731
static void DumpMatches(RuleAnalyzer *ctx, SCJsonBuilder *js, const SigMatchData *smd)
UNCOV
732
{
×
UNCOV
733
    if (smd == NULL)
×
UNCOV
734
        return;
×
735

UNCOV
736
    SCJbOpenArray(js, "matches");
×
UNCOV
737
    do {
×
UNCOV
738
        SCJbStartObject(js);
×
UNCOV
739
        const char *mname = sigmatch_table[smd->type].name;
×
UNCOV
740
        SCJbSetString(js, "name", mname);
×
741

UNCOV
742
        switch (smd->type) {
×
UNCOV
743
            case DETECT_CONTENT: {
×
UNCOV
744
                const DetectContentData *cd = (const DetectContentData *)smd->ctx;
×
745

UNCOV
746
                SCJbOpenObject(js, "content");
×
UNCOV
747
                DumpContent(js, cd);
×
UNCOV
748
                if (cd->flags & DETECT_CONTENT_FAST_PATTERN_ONLY) {
×
UNCOV
749
                    AnalyzerNote(ctx, (char *)"'fast_pattern:only' option is silently ignored and "
×
UNCOV
750
                                              "is interpreted as regular 'fast_pattern'");
×
UNCOV
751
                }
×
UNCOV
752
                if (LooksLikeHTTPMethod(cd->content, cd->content_len)) {
×
UNCOV
753
                    AnalyzerNote(ctx,
×
UNCOV
754
                            (char *)"pattern looks like it inspects HTTP, use http.request_line or "
×
UNCOV
755
                                    "http.method and http.uri instead for improved performance");
×
UNCOV
756
                }
×
UNCOV
757
                if (LooksLikeHTTPUA(cd->content, cd->content_len)) {
×
758
                    AnalyzerNote(ctx,
×
759
                            (char *)"pattern looks like it inspects HTTP, use http.user_agent "
×
760
                                    "or http.header for improved performance");
×
761
                }
×
UNCOV
762
                if (cd->flags & DETECT_CONTENT_WITHIN2DEPTH) {
×
UNCOV
763
                    AnalyzerNote(ctx, (char *)"'within' option for pattern w/o previous content "
×
UNCOV
764
                                              "was converted to 'depth'");
×
UNCOV
765
                }
×
UNCOV
766
                if (cd->flags & DETECT_CONTENT_DISTANCE2OFFSET) {
×
UNCOV
767
                    AnalyzerNote(ctx, (char *)"'distance' option for pattern w/o previous content "
×
UNCOV
768
                                              "was converted to 'offset'");
×
UNCOV
769
                }
×
UNCOV
770
                SCJbClose(js);
×
UNCOV
771
                break;
×
772
            }
×
UNCOV
773
            case DETECT_PCRE: {
×
UNCOV
774
                const DetectPcreData *cd = (const DetectPcreData *)smd->ctx;
×
775

UNCOV
776
                SCJbOpenObject(js, "pcre");
×
UNCOV
777
                DumpPcre(js, cd);
×
UNCOV
778
                SCJbClose(js);
×
UNCOV
779
                if (cd->flags & DETECT_PCRE_RAWBYTES) {
×
UNCOV
780
                    AnalyzerNote(ctx,
×
UNCOV
781
                            (char *)"'/B' (rawbytes) option is a no-op and is silently ignored");
×
UNCOV
782
                }
×
UNCOV
783
                break;
×
784
            }
×
UNCOV
785
            case DETECT_BYTEJUMP: {
×
UNCOV
786
                const DetectBytejumpData *cd = (const DetectBytejumpData *)smd->ctx;
×
787

UNCOV
788
                SCJbOpenObject(js, "byte_jump");
×
UNCOV
789
                SCJbSetUint(js, "nbytes", cd->nbytes);
×
UNCOV
790
                SCJbSetInt(js, "offset", cd->offset);
×
UNCOV
791
                SCJbSetUint(js, "multiplier", cd->multiplier);
×
UNCOV
792
                SCJbSetInt(js, "post_offset", cd->post_offset);
×
UNCOV
793
                switch (cd->base) {
×
UNCOV
794
                    case DETECT_BYTEJUMP_BASE_UNSET:
×
UNCOV
795
                        SCJbSetString(js, "base", "unset");
×
UNCOV
796
                        break;
×
797
                    case DETECT_BYTEJUMP_BASE_OCT:
×
798
                        SCJbSetString(js, "base", "oct");
×
799
                        break;
×
800
                    case DETECT_BYTEJUMP_BASE_DEC:
×
801
                        SCJbSetString(js, "base", "dec");
×
802
                        break;
×
803
                    case DETECT_BYTEJUMP_BASE_HEX:
×
804
                        SCJbSetString(js, "base", "hex");
×
805
                        break;
×
UNCOV
806
                }
×
UNCOV
807
                SCJbOpenArray(js, "flags");
×
UNCOV
808
                if (cd->flags & DETECT_BYTEJUMP_BEGIN)
×
809
                    SCJbAppendString(js, "from_beginning");
×
UNCOV
810
                if (cd->flags & DETECT_BYTEJUMP_LITTLE)
×
811
                    SCJbAppendString(js, "little_endian");
×
UNCOV
812
                if (cd->flags & DETECT_BYTEJUMP_BIG)
×
813
                    SCJbAppendString(js, "big_endian");
×
UNCOV
814
                if (cd->flags & DETECT_BYTEJUMP_STRING)
×
815
                    SCJbAppendString(js, "string");
×
UNCOV
816
                if (cd->flags & DETECT_BYTEJUMP_RELATIVE)
×
UNCOV
817
                    SCJbAppendString(js, "relative");
×
UNCOV
818
                if (cd->flags & DETECT_BYTEJUMP_ALIGN)
×
UNCOV
819
                    SCJbAppendString(js, "align");
×
UNCOV
820
                if (cd->flags & DETECT_BYTEJUMP_DCE)
×
UNCOV
821
                    SCJbAppendString(js, "dce");
×
UNCOV
822
                if (cd->flags & DETECT_BYTEJUMP_OFFSET_BE)
×
823
                    SCJbAppendString(js, "offset_be");
×
UNCOV
824
                if (cd->flags & DETECT_BYTEJUMP_END)
×
825
                    SCJbAppendString(js, "from_end");
×
UNCOV
826
                SCJbClose(js);
×
UNCOV
827
                SCJbClose(js);
×
UNCOV
828
                break;
×
UNCOV
829
            }
×
UNCOV
830
            case DETECT_BYTETEST: {
×
UNCOV
831
                const DetectBytetestData *cd = (const DetectBytetestData *)smd->ctx;
×
832

UNCOV
833
                SCJbOpenObject(js, "byte_test");
×
UNCOV
834
                SCJbSetUint(js, "nbytes", cd->nbytes);
×
UNCOV
835
                SCJbSetInt(js, "offset", cd->offset);
×
UNCOV
836
                switch (cd->base) {
×
UNCOV
837
                    case DETECT_BYTETEST_BASE_UNSET:
×
UNCOV
838
                        SCJbSetString(js, "base", "unset");
×
UNCOV
839
                        break;
×
840
                    case DETECT_BYTETEST_BASE_OCT:
×
841
                        SCJbSetString(js, "base", "oct");
×
842
                        break;
×
843
                    case DETECT_BYTETEST_BASE_DEC:
×
844
                        SCJbSetString(js, "base", "dec");
×
845
                        break;
×
846
                    case DETECT_BYTETEST_BASE_HEX:
×
847
                        SCJbSetString(js, "base", "hex");
×
848
                        break;
×
UNCOV
849
                }
×
UNCOV
850
                SCJbOpenArray(js, "flags");
×
UNCOV
851
                if (cd->flags & DETECT_BYTETEST_LITTLE)
×
UNCOV
852
                    SCJbAppendString(js, "little_endian");
×
UNCOV
853
                if (cd->flags & DETECT_BYTETEST_BIG)
×
854
                    SCJbAppendString(js, "big_endian");
×
UNCOV
855
                if (cd->flags & DETECT_BYTETEST_STRING)
×
856
                    SCJbAppendString(js, "string");
×
UNCOV
857
                if (cd->flags & DETECT_BYTETEST_RELATIVE)
×
UNCOV
858
                    SCJbAppendString(js, "relative");
×
UNCOV
859
                if (cd->flags & DETECT_BYTETEST_DCE)
×
UNCOV
860
                    SCJbAppendString(js, "dce");
×
UNCOV
861
                SCJbClose(js);
×
UNCOV
862
                SCJbClose(js);
×
UNCOV
863
                break;
×
UNCOV
864
            }
×
UNCOV
865
            case DETECT_ABSENT: {
×
UNCOV
866
                const DetectAbsentData *dad = (const DetectAbsentData *)smd->ctx;
×
UNCOV
867
                SCJbOpenObject(js, "absent");
×
UNCOV
868
                SCJbSetBool(js, "or_else", dad->or_else);
×
UNCOV
869
                SCJbClose(js);
×
UNCOV
870
                break;
×
UNCOV
871
            }
×
872

UNCOV
873
            case DETECT_IPOPTS: {
×
UNCOV
874
                const DetectIpOptsData *cd = (const DetectIpOptsData *)smd->ctx;
×
875

UNCOV
876
                SCJbOpenObject(js, "ipopts");
×
UNCOV
877
                const char *flag = IpOptsFlagToString(cd->ipopt);
×
UNCOV
878
                SCJbSetString(js, "option", flag);
×
UNCOV
879
                SCJbClose(js);
×
UNCOV
880
                break;
×
UNCOV
881
            }
×
UNCOV
882
            case DETECT_FLOWBITS: {
×
UNCOV
883
                const DetectFlowbitsData *cd = (const DetectFlowbitsData *)smd->ctx;
×
884

UNCOV
885
                SCJbOpenObject(js, "flowbits");
×
UNCOV
886
                switch (cd->cmd) {
×
UNCOV
887
                    case DETECT_FLOWBITS_CMD_ISSET:
×
UNCOV
888
                        SCJbSetString(js, "cmd", "isset");
×
UNCOV
889
                        break;
×
UNCOV
890
                    case DETECT_FLOWBITS_CMD_ISNOTSET:
×
UNCOV
891
                        SCJbSetString(js, "cmd", "isnotset");
×
UNCOV
892
                        break;
×
UNCOV
893
                    case DETECT_FLOWBITS_CMD_SET:
×
UNCOV
894
                        SCJbSetString(js, "cmd", "set");
×
UNCOV
895
                        break;
×
UNCOV
896
                    case DETECT_FLOWBITS_CMD_UNSET:
×
UNCOV
897
                        SCJbSetString(js, "cmd", "unset");
×
UNCOV
898
                        break;
×
UNCOV
899
                    case DETECT_FLOWBITS_CMD_TOGGLE:
×
UNCOV
900
                        SCJbSetString(js, "cmd", "toggle");
×
UNCOV
901
                        break;
×
UNCOV
902
                }
×
UNCOV
903
                bool is_or = false;
×
UNCOV
904
                SCJbOpenArray(js, "names");
×
UNCOV
905
                if (cd->or_list_size == 0) {
×
UNCOV
906
                    SCJbAppendString(js, VarNameStoreSetupLookup(cd->idx, VAR_TYPE_FLOW_BIT));
×
UNCOV
907
                } else if (cd->or_list_size > 0) {
×
UNCOV
908
                    is_or = true;
×
UNCOV
909
                    for (uint8_t i = 0; i < cd->or_list_size; i++) {
×
UNCOV
910
                        const char *varname =
×
UNCOV
911
                                VarNameStoreSetupLookup(cd->or_list[i], VAR_TYPE_FLOW_BIT);
×
UNCOV
912
                        SCJbAppendString(js, varname);
×
UNCOV
913
                    }
×
UNCOV
914
                }
×
UNCOV
915
                SCJbClose(js); // array
×
UNCOV
916
                if (is_or) {
×
UNCOV
917
                    SCJbSetString(js, "operator", "or");
×
UNCOV
918
                }
×
UNCOV
919
                SCJbClose(js); // object
×
UNCOV
920
                break;
×
UNCOV
921
            }
×
UNCOV
922
            case DETECT_XBITS: {
×
UNCOV
923
                const DetectXbitsData *xd = (const DetectXbitsData *)smd->ctx;
×
924

UNCOV
925
                SCJbOpenObject(js, "xbits");
×
UNCOV
926
                switch (xd->cmd) {
×
UNCOV
927
                    case DETECT_XBITS_CMD_ISSET:
×
UNCOV
928
                        SCJbSetString(js, "cmd", "isset");
×
UNCOV
929
                        break;
×
UNCOV
930
                    case DETECT_XBITS_CMD_ISNOTSET:
×
UNCOV
931
                        SCJbSetString(js, "cmd", "isnotset");
×
UNCOV
932
                        break;
×
UNCOV
933
                    case DETECT_XBITS_CMD_SET:
×
UNCOV
934
                        SCJbSetString(js, "cmd", "set");
×
UNCOV
935
                        break;
×
UNCOV
936
                    case DETECT_XBITS_CMD_UNSET:
×
UNCOV
937
                        SCJbSetString(js, "cmd", "unset");
×
UNCOV
938
                        break;
×
UNCOV
939
                    case DETECT_XBITS_CMD_TOGGLE:
×
UNCOV
940
                        SCJbSetString(js, "cmd", "toggle");
×
UNCOV
941
                        break;
×
UNCOV
942
                }
×
UNCOV
943
                SCJbSetString(js, "name", VarNameStoreSetupLookup(xd->idx, xd->type));
×
UNCOV
944
                switch (xd->tracker) {
×
UNCOV
945
                    case DETECT_XBITS_TRACK_IPSRC:
×
UNCOV
946
                        SCJbSetString(js, "track", "ip_src");
×
UNCOV
947
                        break;
×
UNCOV
948
                    case DETECT_XBITS_TRACK_IPDST:
×
UNCOV
949
                        SCJbSetString(js, "track", "ip_dst");
×
UNCOV
950
                        break;
×
UNCOV
951
                    case DETECT_XBITS_TRACK_IPPAIR:
×
UNCOV
952
                        SCJbSetString(js, "track", "ip_pair");
×
UNCOV
953
                        break;
×
954
                    case DETECT_XBITS_TRACK_TX:
×
955
                        SCJbSetString(js, "track", "tx");
×
956
                        break;
×
UNCOV
957
                }
×
958
                // always log expire value
UNCOV
959
                SCJbSetUint(js, "expire", xd->expire);
×
UNCOV
960
                SCJbClose(js); // object
×
UNCOV
961
                break;
×
UNCOV
962
            }
×
UNCOV
963
            case DETECT_FLOWINT: {
×
UNCOV
964
                const DetectFlowintData *cd = (const DetectFlowintData *)smd->ctx;
×
965

UNCOV
966
                SCJbOpenObject(js, "flowint");
×
UNCOV
967
                switch (cd->modifier) {
×
UNCOV
968
                    case FLOWINT_MODIFIER_SET:
×
UNCOV
969
                        SCJbSetString(js, "cmd", "set");
×
UNCOV
970
                        break;
×
UNCOV
971
                    case FLOWINT_MODIFIER_ADD:
×
UNCOV
972
                        SCJbSetString(js, "cmd", "add");
×
UNCOV
973
                        break;
×
UNCOV
974
                    case FLOWINT_MODIFIER_SUB:
×
UNCOV
975
                        SCJbSetString(js, "cmd", "sub");
×
UNCOV
976
                        break;
×
UNCOV
977
                    case FLOWINT_MODIFIER_LT:
×
UNCOV
978
                        SCJbSetString(js, "cmd", "lt");
×
UNCOV
979
                        break;
×
UNCOV
980
                    case FLOWINT_MODIFIER_LE:
×
UNCOV
981
                        SCJbSetString(js, "cmd", "lte");
×
UNCOV
982
                        break;
×
UNCOV
983
                    case FLOWINT_MODIFIER_EQ:
×
UNCOV
984
                        SCJbSetString(js, "cmd", "eq");
×
UNCOV
985
                        break;
×
UNCOV
986
                    case FLOWINT_MODIFIER_NE:
×
UNCOV
987
                        SCJbSetString(js, "cmd", "ne");
×
UNCOV
988
                        break;
×
UNCOV
989
                    case FLOWINT_MODIFIER_GE:
×
UNCOV
990
                        SCJbSetString(js, "cmd", "gte");
×
UNCOV
991
                        break;
×
UNCOV
992
                    case FLOWINT_MODIFIER_GT:
×
UNCOV
993
                        SCJbSetString(js, "cmd", "gt");
×
UNCOV
994
                        break;
×
UNCOV
995
                    case FLOWINT_MODIFIER_ISSET:
×
UNCOV
996
                        SCJbSetString(js, "cmd", "isset");
×
UNCOV
997
                        break;
×
UNCOV
998
                    case FLOWINT_MODIFIER_ISNOTSET:
×
UNCOV
999
                        SCJbSetString(js, "cmd", "isnotset");
×
UNCOV
1000
                        break;
×
UNCOV
1001
                }
×
UNCOV
1002
                const char *varname = VarNameStoreSetupLookup(cd->idx, VAR_TYPE_FLOW_INT);
×
UNCOV
1003
                if (varname != NULL) {
×
UNCOV
1004
                    SCJbSetString(js, "var", varname);
×
UNCOV
1005
                }
×
UNCOV
1006
                if (cd->targettype == FLOWINT_TARGET_VAL) {
×
UNCOV
1007
                    SCJbSetUint(js, "value", cd->target.value);
×
UNCOV
1008
                } else if (cd->targettype == FLOWINT_TARGET_VAR) {
×
1009
                    if (cd->target.tvar.name != NULL) {
×
1010
                        SCJbSetString(js, "target", cd->target.tvar.name);
×
1011
                    }
×
1012
                }
×
UNCOV
1013
                SCJbClose(js);
×
UNCOV
1014
                break;
×
UNCOV
1015
            }
×
UNCOV
1016
            case DETECT_ACK: {
×
UNCOV
1017
                const DetectU32Data *cd = (const DetectU32Data *)smd->ctx;
×
UNCOV
1018
                SCJbOpenObject(js, "ack");
×
UNCOV
1019
                SCDetectU32ToJson(js, cd);
×
UNCOV
1020
                SCJbClose(js);
×
UNCOV
1021
                break;
×
UNCOV
1022
            }
×
UNCOV
1023
            case DETECT_SEQ: {
×
UNCOV
1024
                const DetectU32Data *cd = (const DetectU32Data *)smd->ctx;
×
UNCOV
1025
                SCJbOpenObject(js, "seq");
×
UNCOV
1026
                SCDetectU32ToJson(js, cd);
×
UNCOV
1027
                SCJbClose(js);
×
UNCOV
1028
                break;
×
UNCOV
1029
            }
×
UNCOV
1030
            case DETECT_TCPMSS: {
×
UNCOV
1031
                const DetectU16Data *cd = (const DetectU16Data *)smd->ctx;
×
UNCOV
1032
                SCJbOpenObject(js, "tcp_mss");
×
UNCOV
1033
                SCDetectU16ToJson(js, cd);
×
UNCOV
1034
                SCJbClose(js);
×
UNCOV
1035
                break;
×
UNCOV
1036
            }
×
UNCOV
1037
            case DETECT_DSIZE: {
×
UNCOV
1038
                const DetectU16Data *cd = (const DetectU16Data *)smd->ctx;
×
UNCOV
1039
                SCJbOpenObject(js, "dsize");
×
UNCOV
1040
                SCDetectU16ToJson(js, cd);
×
UNCOV
1041
                SCJbClose(js);
×
UNCOV
1042
                break;
×
UNCOV
1043
            }
×
UNCOV
1044
            case DETECT_ICODE: {
×
UNCOV
1045
                const DetectU8Data *cd = (const DetectU8Data *)smd->ctx;
×
UNCOV
1046
                SCJbOpenObject(js, "code");
×
UNCOV
1047
                SCDetectU8ToJson(js, cd);
×
UNCOV
1048
                SCJbClose(js);
×
UNCOV
1049
                break;
×
UNCOV
1050
            }
×
UNCOV
1051
            case DETECT_TTL: {
×
UNCOV
1052
                const DetectU8Data *cd = (const DetectU8Data *)smd->ctx;
×
UNCOV
1053
                SCJbOpenObject(js, "ttl");
×
UNCOV
1054
                SCDetectU8ToJson(js, cd);
×
UNCOV
1055
                SCJbClose(js);
×
UNCOV
1056
                break;
×
UNCOV
1057
            }
×
UNCOV
1058
            case DETECT_ICMP_ID: {
×
UNCOV
1059
                const DetectU16Data *cd = (const DetectU16Data *)smd->ctx;
×
UNCOV
1060
                SCJbOpenObject(js, "id");
×
UNCOV
1061
                SCDetectU16ToJson(js, cd);
×
UNCOV
1062
                SCJbClose(js);
×
UNCOV
1063
                break;
×
UNCOV
1064
            }
×
UNCOV
1065
            case DETECT_WINDOW: {
×
UNCOV
1066
                const DetectU16Data *cd = (const DetectU16Data *)smd->ctx;
×
UNCOV
1067
                SCJbOpenObject(js, "window");
×
UNCOV
1068
                SCDetectU16ToJson(js, cd);
×
UNCOV
1069
                SCJbClose(js);
×
UNCOV
1070
                break;
×
UNCOV
1071
            }
×
UNCOV
1072
            case DETECT_FLOW_AGE: {
×
UNCOV
1073
                const DetectU32Data *cd = (const DetectU32Data *)smd->ctx;
×
UNCOV
1074
                SCJbOpenObject(js, "flow_age");
×
UNCOV
1075
                SCDetectU32ToJson(js, cd);
×
UNCOV
1076
                SCJbClose(js);
×
UNCOV
1077
                break;
×
UNCOV
1078
            }
×
UNCOV
1079
            case DETECT_FLOW_ELEPHANT: {
×
UNCOV
1080
                const uint8_t *dfd = (const uint8_t *)smd->ctx;
×
UNCOV
1081
                SCJbOpenObject(js, "flow_elephant");
×
UNCOV
1082
                switch (*dfd) {
×
UNCOV
1083
                    case DETECT_FLOW_TOSERVER:
×
UNCOV
1084
                        SCJbSetString(js, "dir", "toserver");
×
UNCOV
1085
                        break;
×
UNCOV
1086
                    case DETECT_FLOW_TOCLIENT:
×
UNCOV
1087
                        SCJbSetString(js, "dir", "toclient");
×
UNCOV
1088
                        break;
×
UNCOV
1089
                    case DETECT_FLOW_TOEITHER:
×
UNCOV
1090
                        SCJbSetString(js, "dir", "either");
×
UNCOV
1091
                        break;
×
UNCOV
1092
                    case DETECT_FLOW_TOBOTH:
×
UNCOV
1093
                        SCJbSetString(js, "dir", "both");
×
UNCOV
1094
                        break;
×
UNCOV
1095
                }
×
UNCOV
1096
                SCJbClose(js);
×
UNCOV
1097
                break;
×
UNCOV
1098
            }
×
UNCOV
1099
        }
×
UNCOV
1100
        SCJbClose(js);
×
1101

UNCOV
1102
        if (smd->is_last)
×
UNCOV
1103
            break;
×
UNCOV
1104
        smd++;
×
UNCOV
1105
    } while (1);
×
UNCOV
1106
    SCJbClose(js);
×
UNCOV
1107
}
×
1108

1109
SCMutex g_rules_analyzer_write_m = SCMUTEX_INITIALIZER;
1110
void EngineAnalysisRules2(const DetectEngineCtx *de_ctx, const Signature *s)
UNCOV
1111
{
×
UNCOV
1112
    SCEnter();
×
1113

UNCOV
1114
    RuleAnalyzer ctx = { NULL, NULL, NULL };
×
1115

UNCOV
1116
    ctx.js = SCJbNewObject();
×
UNCOV
1117
    if (ctx.js == NULL)
×
1118
        SCReturn;
×
1119

UNCOV
1120
    if (s->init_data->firewall_rule) {
×
1121
        JB_SET_STRING(ctx.js, "class", "firewall");
×
UNCOV
1122
    } else {
×
UNCOV
1123
        JB_SET_STRING(ctx.js, "class", "threat detection");
×
UNCOV
1124
    }
×
1125

UNCOV
1126
    SCJbSetString(ctx.js, "raw", s->sig_str);
×
UNCOV
1127
    SCJbSetUint(ctx.js, "id", s->id);
×
UNCOV
1128
    SCJbSetUint(ctx.js, "gid", s->gid);
×
UNCOV
1129
    SCJbSetUint(ctx.js, "rev", s->rev);
×
UNCOV
1130
    SCJbSetString(ctx.js, "msg", s->msg);
×
1131

UNCOV
1132
    const char *alproto = AppProtoToString(s->alproto);
×
UNCOV
1133
    SCJbSetString(ctx.js, "app_proto", alproto);
×
1134

UNCOV
1135
    SCJbOpenArray(ctx.js, "requirements");
×
UNCOV
1136
    if (s->mask & SIG_MASK_REQUIRE_PAYLOAD) {
×
UNCOV
1137
        SCJbAppendString(ctx.js, "payload");
×
UNCOV
1138
    }
×
UNCOV
1139
    if (s->mask & SIG_MASK_REQUIRE_NO_PAYLOAD) {
×
UNCOV
1140
        SCJbAppendString(ctx.js, "no_payload");
×
UNCOV
1141
    }
×
UNCOV
1142
    if (s->mask & SIG_MASK_REQUIRE_FLOW) {
×
UNCOV
1143
        SCJbAppendString(ctx.js, "flow");
×
UNCOV
1144
    }
×
UNCOV
1145
    if (s->mask & SIG_MASK_REQUIRE_FLAGS_INITDEINIT) {
×
UNCOV
1146
        SCJbAppendString(ctx.js, "tcp_flags_init_deinit");
×
UNCOV
1147
    }
×
UNCOV
1148
    if (s->mask & SIG_MASK_REQUIRE_FLAGS_UNUSUAL) {
×
1149
        SCJbAppendString(ctx.js, "tcp_flags_unusual");
×
1150
    }
×
UNCOV
1151
    if (s->mask & SIG_MASK_REQUIRE_ENGINE_EVENT) {
×
UNCOV
1152
        SCJbAppendString(ctx.js, "engine_event");
×
UNCOV
1153
    }
×
UNCOV
1154
    if (s->mask & SIG_MASK_REQUIRE_REAL_PKT) {
×
UNCOV
1155
        SCJbAppendString(ctx.js, "real_pkt");
×
UNCOV
1156
    }
×
UNCOV
1157
    SCJbClose(ctx.js);
×
1158

UNCOV
1159
    SCJbOpenObject(ctx.js, "match_policy");
×
UNCOV
1160
    SCJbOpenArray(ctx.js, "actions");
×
UNCOV
1161
    if (s->action & ACTION_ALERT) {
×
UNCOV
1162
        SCJbAppendString(ctx.js, "alert");
×
UNCOV
1163
    }
×
UNCOV
1164
    if (s->action & ACTION_DROP) {
×
UNCOV
1165
        SCJbAppendString(ctx.js, "drop");
×
UNCOV
1166
    }
×
UNCOV
1167
    if (s->action & ACTION_REJECT) {
×
1168
        SCJbAppendString(ctx.js, "reject");
×
1169
    }
×
UNCOV
1170
    if (s->action & ACTION_REJECT_DST) {
×
1171
        SCJbAppendString(ctx.js, "reject_dst");
×
1172
    }
×
UNCOV
1173
    if (s->action & ACTION_REJECT_BOTH) {
×
1174
        SCJbAppendString(ctx.js, "reject_both");
×
1175
    }
×
UNCOV
1176
    if (s->action & ACTION_CONFIG) {
×
1177
        SCJbAppendString(ctx.js, "config");
×
1178
    }
×
UNCOV
1179
    if (s->action & ACTION_PASS) {
×
UNCOV
1180
        SCJbAppendString(ctx.js, "pass");
×
UNCOV
1181
    }
×
UNCOV
1182
    if (s->action & ACTION_ACCEPT) {
×
1183
        SCJbAppendString(ctx.js, "accept");
×
1184
    }
×
UNCOV
1185
    SCJbClose(ctx.js);
×
1186

UNCOV
1187
    if (s->action_scope == ACTION_SCOPE_AUTO) {
×
UNCOV
1188
        enum SignaturePropertyFlowAction flow_action = signature_properties[s->type].flow_action;
×
UNCOV
1189
        switch (flow_action) {
×
UNCOV
1190
            case SIG_PROP_FLOW_ACTION_PACKET:
×
UNCOV
1191
                SCJbSetString(ctx.js, "scope", "packet");
×
UNCOV
1192
                break;
×
UNCOV
1193
            case SIG_PROP_FLOW_ACTION_FLOW:
×
UNCOV
1194
                SCJbSetString(ctx.js, "scope", "flow");
×
UNCOV
1195
                break;
×
UNCOV
1196
            case SIG_PROP_FLOW_ACTION_FLOW_IF_STATEFUL:
×
UNCOV
1197
                SCJbSetString(ctx.js, "scope", "flow_if_stateful");
×
UNCOV
1198
                break;
×
UNCOV
1199
        }
×
UNCOV
1200
    } else {
×
1201
        enum ActionScope as = s->action_scope;
×
1202
        switch (as) {
×
1203
            case ACTION_SCOPE_PACKET:
×
1204
                SCJbSetString(ctx.js, "scope", "packet");
×
1205
                break;
×
1206
            case ACTION_SCOPE_FLOW:
×
1207
                SCJbSetString(ctx.js, "scope", "flow");
×
1208
                break;
×
1209
            case ACTION_SCOPE_HOOK:
×
1210
                SCJbSetString(ctx.js, "scope", "hook");
×
1211
                break;
×
1212
            case ACTION_SCOPE_TX:
×
1213
                SCJbSetString(ctx.js, "scope", "tx");
×
1214
                break;
×
1215
            case ACTION_SCOPE_AUTO: /* should be unreachable */
×
1216
                break;
×
1217
        }
×
1218
    }
×
UNCOV
1219
    SCJbClose(ctx.js);
×
1220

UNCOV
1221
    switch (s->type) {
×
1222
        case SIG_TYPE_NOT_SET:
×
1223
            SCJbSetString(ctx.js, "type", "unset");
×
1224
            break;
×
UNCOV
1225
        case SIG_TYPE_IPONLY:
×
UNCOV
1226
            SCJbSetString(ctx.js, "type", "ip_only");
×
UNCOV
1227
            break;
×
UNCOV
1228
        case SIG_TYPE_LIKE_IPONLY:
×
UNCOV
1229
            SCJbSetString(ctx.js, "type", "like_ip_only");
×
UNCOV
1230
            break;
×
UNCOV
1231
        case SIG_TYPE_PDONLY:
×
UNCOV
1232
            SCJbSetString(ctx.js, "type", "pd_only");
×
UNCOV
1233
            break;
×
UNCOV
1234
        case SIG_TYPE_DEONLY:
×
UNCOV
1235
            SCJbSetString(ctx.js, "type", "de_only");
×
UNCOV
1236
            break;
×
UNCOV
1237
        case SIG_TYPE_PKT:
×
UNCOV
1238
            SCJbSetString(ctx.js, "type", "pkt");
×
UNCOV
1239
            break;
×
UNCOV
1240
        case SIG_TYPE_PKT_STREAM:
×
UNCOV
1241
            SCJbSetString(ctx.js, "type", "pkt_stream");
×
UNCOV
1242
            break;
×
UNCOV
1243
        case SIG_TYPE_STREAM:
×
UNCOV
1244
            SCJbSetString(ctx.js, "type", "stream");
×
UNCOV
1245
            break;
×
UNCOV
1246
        case SIG_TYPE_APPLAYER:
×
UNCOV
1247
            SCJbSetString(ctx.js, "type", "app_layer");
×
UNCOV
1248
            break;
×
UNCOV
1249
        case SIG_TYPE_APP_TX:
×
UNCOV
1250
            SCJbSetString(ctx.js, "type", "app_tx");
×
UNCOV
1251
            break;
×
1252
        case SIG_TYPE_MAX:
×
1253
            SCJbSetString(ctx.js, "type", "error");
×
1254
            break;
×
UNCOV
1255
    }
×
1256

1257
    // dependencies object and its subfields only logged if we have values
UNCOV
1258
    if (s->init_data->is_rule_state_dependant) {
×
UNCOV
1259
        SCJbOpenObject(ctx.js, "dependencies");
×
UNCOV
1260
        SCJbOpenObject(ctx.js, "flowbits");
×
UNCOV
1261
        SCJbOpenObject(ctx.js, "upstream");
×
UNCOV
1262
        if (s->init_data->rule_state_dependant_sids_size > 0) {
×
UNCOV
1263
            SCJbOpenObject(ctx.js, "state_modifying_rules");
×
UNCOV
1264
            SCJbOpenArray(ctx.js, "sids");
×
UNCOV
1265
            for (uint32_t i = 0; i < s->init_data->rule_state_dependant_sids_idx; i++) {
×
UNCOV
1266
                SCJbAppendUint(ctx.js, s->init_data->rule_state_dependant_sids_array[i]);
×
UNCOV
1267
            }
×
UNCOV
1268
            SCJbClose(ctx.js); // sids
×
UNCOV
1269
            SCJbOpenArray(ctx.js, "names");
×
UNCOV
1270
            for (uint32_t i = 0; i < s->init_data->rule_state_flowbits_ids_size - 1; i++) {
×
UNCOV
1271
                if (s->init_data->rule_state_flowbits_ids_array[i] != 0) {
×
UNCOV
1272
                    SCJbAppendString(ctx.js,
×
UNCOV
1273
                            VarNameStoreSetupLookup(s->init_data->rule_state_flowbits_ids_array[i],
×
UNCOV
1274
                                    VAR_TYPE_FLOW_BIT));
×
UNCOV
1275
                }
×
UNCOV
1276
            }
×
UNCOV
1277
            SCJbClose(ctx.js); // names
×
UNCOV
1278
            SCJbClose(ctx.js); // state_modifying_rules
×
UNCOV
1279
        }
×
UNCOV
1280
        SCJbClose(ctx.js); // upstream
×
UNCOV
1281
        SCJbClose(ctx.js); // flowbits
×
UNCOV
1282
        SCJbClose(ctx.js); // dependencies
×
UNCOV
1283
    }
×
1284

UNCOV
1285
    SCJbOpenArray(ctx.js, "flags");
×
UNCOV
1286
    if (s->flags & SIG_FLAG_SRC_ANY) {
×
UNCOV
1287
        SCJbAppendString(ctx.js, "src_any");
×
UNCOV
1288
    }
×
UNCOV
1289
    if (s->flags & SIG_FLAG_DST_ANY) {
×
UNCOV
1290
        SCJbAppendString(ctx.js, "dst_any");
×
UNCOV
1291
    }
×
UNCOV
1292
    if (s->flags & SIG_FLAG_SP_ANY) {
×
UNCOV
1293
        SCJbAppendString(ctx.js, "sp_any");
×
UNCOV
1294
    }
×
UNCOV
1295
    if (s->flags & SIG_FLAG_DP_ANY) {
×
UNCOV
1296
        SCJbAppendString(ctx.js, "dp_any");
×
UNCOV
1297
    }
×
UNCOV
1298
    if ((s->action & ACTION_ALERT) == 0) {
×
UNCOV
1299
        SCJbAppendString(ctx.js, "noalert");
×
UNCOV
1300
    }
×
UNCOV
1301
    if (s->flags & SIG_FLAG_DSIZE) {
×
UNCOV
1302
        SCJbAppendString(ctx.js, "dsize");
×
UNCOV
1303
    }
×
UNCOV
1304
    if (s->flags & SIG_FLAG_APPLAYER) {
×
UNCOV
1305
        SCJbAppendString(ctx.js, "applayer");
×
UNCOV
1306
    }
×
UNCOV
1307
    if (s->flags & SIG_FLAG_REQUIRE_PACKET) {
×
UNCOV
1308
        SCJbAppendString(ctx.js, "need_packet");
×
UNCOV
1309
    }
×
UNCOV
1310
    if (s->flags & SIG_FLAG_REQUIRE_STREAM) {
×
UNCOV
1311
        SCJbAppendString(ctx.js, "need_stream");
×
UNCOV
1312
    }
×
UNCOV
1313
    if (s->flags & SIG_FLAG_MPM_NEG) {
×
UNCOV
1314
        SCJbAppendString(ctx.js, "negated_mpm");
×
UNCOV
1315
    }
×
UNCOV
1316
    if (s->flags & SIG_FLAG_FLUSH) {
×
UNCOV
1317
        SCJbAppendString(ctx.js, "flush");
×
UNCOV
1318
    }
×
UNCOV
1319
    if (s->flags & SIG_FLAG_REQUIRE_FLOWVAR) {
×
UNCOV
1320
        SCJbAppendString(ctx.js, "need_flowvar");
×
UNCOV
1321
    }
×
UNCOV
1322
    if (s->flags & SIG_FLAG_FILESTORE) {
×
UNCOV
1323
        SCJbAppendString(ctx.js, "filestore");
×
UNCOV
1324
    }
×
UNCOV
1325
    if (s->flags & SIG_FLAG_TOSERVER) {
×
UNCOV
1326
        SCJbAppendString(ctx.js, "toserver");
×
UNCOV
1327
    }
×
UNCOV
1328
    if (s->flags & SIG_FLAG_TOCLIENT) {
×
UNCOV
1329
        SCJbAppendString(ctx.js, "toclient");
×
UNCOV
1330
    }
×
UNCOV
1331
    if (s->flags & SIG_FLAG_TLSSTORE) {
×
1332
        SCJbAppendString(ctx.js, "tlsstore");
×
1333
    }
×
UNCOV
1334
    if (s->flags & SIG_FLAG_BYPASS) {
×
1335
        SCJbAppendString(ctx.js, "bypass");
×
1336
    }
×
UNCOV
1337
    if (s->flags & SIG_FLAG_PREFILTER) {
×
UNCOV
1338
        SCJbAppendString(ctx.js, "prefilter");
×
UNCOV
1339
    }
×
UNCOV
1340
    if (s->flags & SIG_FLAG_SRC_IS_TARGET) {
×
1341
        SCJbAppendString(ctx.js, "src_is_target");
×
1342
    }
×
UNCOV
1343
    if (s->flags & SIG_FLAG_DEST_IS_TARGET) {
×
1344
        SCJbAppendString(ctx.js, "dst_is_target");
×
1345
    }
×
UNCOV
1346
    SCJbClose(ctx.js);
×
1347

UNCOV
1348
    const DetectEnginePktInspectionEngine *pkt_mpm = NULL;
×
UNCOV
1349
    const DetectEngineAppInspectionEngine *app_mpm = NULL;
×
1350

UNCOV
1351
    SCJbOpenArray(ctx.js, "pkt_engines");
×
UNCOV
1352
    const DetectEnginePktInspectionEngine *pkt = s->pkt_inspect;
×
UNCOV
1353
    for ( ; pkt != NULL; pkt = pkt->next) {
×
UNCOV
1354
        const char *name = DetectEngineBufferTypeGetNameById(de_ctx, pkt->sm_list);
×
UNCOV
1355
        if (name == NULL) {
×
UNCOV
1356
            switch (pkt->sm_list) {
×
UNCOV
1357
                case DETECT_SM_LIST_PMATCH:
×
UNCOV
1358
                    name = "payload";
×
UNCOV
1359
                    break;
×
UNCOV
1360
                case DETECT_SM_LIST_MATCH:
×
UNCOV
1361
                    name = "packet";
×
UNCOV
1362
                    break;
×
1363
                default:
×
1364
                    name = "unknown";
×
1365
                    break;
×
UNCOV
1366
            }
×
UNCOV
1367
        }
×
UNCOV
1368
        SCJbStartObject(ctx.js);
×
UNCOV
1369
        SCJbSetString(ctx.js, "name", name);
×
UNCOV
1370
        SCJbSetBool(ctx.js, "is_mpm", pkt->mpm);
×
UNCOV
1371
        if (pkt->v1.transforms != NULL) {
×
1372
            SCJbOpenArray(ctx.js, "transforms");
×
1373
            for (int t = 0; t < pkt->v1.transforms->cnt; t++) {
×
1374
                SCJbStartObject(ctx.js);
×
1375
                SCJbSetString(ctx.js, "name",
×
1376
                        sigmatch_table[pkt->v1.transforms->transforms[t].transform].name);
×
1377
                SCJbClose(ctx.js);
×
1378
            }
×
1379
            SCJbClose(ctx.js);
×
1380
        }
×
UNCOV
1381
        DumpMatches(&ctx, ctx.js, pkt->smd);
×
UNCOV
1382
        SCJbClose(ctx.js);
×
UNCOV
1383
        if (pkt->mpm) {
×
UNCOV
1384
            pkt_mpm = pkt;
×
UNCOV
1385
        }
×
UNCOV
1386
    }
×
UNCOV
1387
    SCJbClose(ctx.js);
×
UNCOV
1388
    SCJbOpenArray(ctx.js, "frame_engines");
×
UNCOV
1389
    const DetectEngineFrameInspectionEngine *frame = s->frame_inspect;
×
UNCOV
1390
    for (; frame != NULL; frame = frame->next) {
×
UNCOV
1391
        const char *name = DetectEngineBufferTypeGetNameById(de_ctx, frame->sm_list);
×
UNCOV
1392
        SCJbStartObject(ctx.js);
×
UNCOV
1393
        SCJbSetString(ctx.js, "name", name);
×
UNCOV
1394
        SCJbSetBool(ctx.js, "is_mpm", frame->mpm);
×
UNCOV
1395
        if (frame->v1.transforms != NULL) {
×
1396
            SCJbOpenArray(ctx.js, "transforms");
×
1397
            for (int t = 0; t < frame->v1.transforms->cnt; t++) {
×
1398
                SCJbStartObject(ctx.js);
×
1399
                SCJbSetString(ctx.js, "name",
×
1400
                        sigmatch_table[frame->v1.transforms->transforms[t].transform].name);
×
1401
                SCJbClose(ctx.js);
×
1402
            }
×
1403
            SCJbClose(ctx.js);
×
1404
        }
×
UNCOV
1405
        DumpMatches(&ctx, ctx.js, frame->smd);
×
UNCOV
1406
        SCJbClose(ctx.js);
×
UNCOV
1407
    }
×
UNCOV
1408
    SCJbClose(ctx.js);
×
1409

UNCOV
1410
    if (s->init_data->init_flags & SIG_FLAG_INIT_STATE_MATCH) {
×
UNCOV
1411
        bool has_stream = false;
×
UNCOV
1412
        bool has_client_body_mpm = false;
×
UNCOV
1413
        bool has_file_data_mpm = false;
×
1414

UNCOV
1415
        SCJbOpenArray(ctx.js, "engines");
×
UNCOV
1416
        const DetectEngineAppInspectionEngine *app = s->app_inspect;
×
UNCOV
1417
        for ( ; app != NULL; app = app->next) {
×
UNCOV
1418
            const char *name = DetectEngineBufferTypeGetNameById(de_ctx, app->sm_list);
×
UNCOV
1419
            if (name == NULL) {
×
UNCOV
1420
                switch (app->sm_list) {
×
UNCOV
1421
                    case DETECT_SM_LIST_PMATCH:
×
UNCOV
1422
                        name = "stream";
×
UNCOV
1423
                        break;
×
1424
                    default:
×
1425
                        name = "unknown";
×
1426
                        break;
×
UNCOV
1427
                }
×
UNCOV
1428
            }
×
1429

UNCOV
1430
            if (app->sm_list == DETECT_SM_LIST_PMATCH && !app->mpm) {
×
UNCOV
1431
                has_stream = true;
×
UNCOV
1432
            } else if (app->mpm && strcmp(name, "http_client_body") == 0) {
×
UNCOV
1433
                has_client_body_mpm = true;
×
UNCOV
1434
            } else if (app->mpm && strcmp(name, "file_data") == 0) {
×
UNCOV
1435
                has_file_data_mpm = true;
×
UNCOV
1436
            }
×
1437

UNCOV
1438
            SCJbStartObject(ctx.js);
×
UNCOV
1439
            SCJbSetString(ctx.js, "name", name);
×
UNCOV
1440
            const char *direction = app->dir == 0 ? "toserver" : "toclient";
×
UNCOV
1441
            SCJbSetString(ctx.js, "direction", direction);
×
UNCOV
1442
            SCJbSetBool(ctx.js, "is_mpm", app->mpm);
×
UNCOV
1443
            SCJbSetString(ctx.js, "app_proto", AppProtoToString(app->alproto));
×
UNCOV
1444
            SCJbSetUint(ctx.js, "progress", app->progress);
×
1445

UNCOV
1446
            if (app->v2.transforms != NULL) {
×
1447
                SCJbOpenArray(ctx.js, "transforms");
×
1448
                for (int t = 0; t < app->v2.transforms->cnt; t++) {
×
1449
                    SCJbStartObject(ctx.js);
×
1450
                    SCJbSetString(ctx.js, "name",
×
1451
                            sigmatch_table[app->v2.transforms->transforms[t].transform].name);
×
1452
                    SCJbClose(ctx.js);
×
1453
                }
×
1454
                SCJbClose(ctx.js);
×
1455
            }
×
UNCOV
1456
            DumpMatches(&ctx, ctx.js, app->smd);
×
UNCOV
1457
            SCJbClose(ctx.js);
×
UNCOV
1458
            if (app->mpm) {
×
UNCOV
1459
                app_mpm = app;
×
UNCOV
1460
            }
×
UNCOV
1461
        }
×
UNCOV
1462
        SCJbClose(ctx.js);
×
1463

UNCOV
1464
        if (has_stream && has_client_body_mpm)
×
1465
            AnalyzerNote(&ctx, (char *)"mpm in http_client_body combined with stream match leads to stream buffering");
×
UNCOV
1466
        if (has_stream && has_file_data_mpm)
×
UNCOV
1467
            AnalyzerNote(&ctx, (char *)"mpm in file_data combined with stream match leads to stream buffering");
×
UNCOV
1468
    }
×
1469

UNCOV
1470
    SCJbOpenObject(ctx.js, "lists");
×
UNCOV
1471
    for (int i = 0; i < DETECT_SM_LIST_MAX; i++) {
×
UNCOV
1472
        if (s->sm_arrays[i] != NULL) {
×
UNCOV
1473
            SCJbOpenObject(ctx.js, DetectListToHumanString(i));
×
UNCOV
1474
            DumpMatches(&ctx, ctx.js, s->sm_arrays[i]);
×
UNCOV
1475
            SCJbClose(ctx.js);
×
UNCOV
1476
        }
×
UNCOV
1477
    }
×
UNCOV
1478
    SCJbClose(ctx.js);
×
1479

UNCOV
1480
    if (pkt_mpm || app_mpm) {
×
UNCOV
1481
        SCJbOpenObject(ctx.js, "mpm");
×
1482

UNCOV
1483
        int mpm_list = pkt_mpm ? DETECT_SM_LIST_PMATCH : app_mpm->sm_list;
×
UNCOV
1484
        const char *name;
×
UNCOV
1485
        if (mpm_list < DETECT_SM_LIST_DYNAMIC_START)
×
UNCOV
1486
            name = DetectListToHumanString(mpm_list);
×
UNCOV
1487
        else
×
UNCOV
1488
            name = DetectEngineBufferTypeGetNameById(de_ctx, mpm_list);
×
UNCOV
1489
        SCJbSetString(ctx.js, "buffer", name);
×
1490

UNCOV
1491
        SigMatchData *smd = pkt_mpm ? pkt_mpm->smd : app_mpm->smd;
×
UNCOV
1492
        if (smd == NULL && mpm_list == DETECT_SM_LIST_PMATCH) {
×
UNCOV
1493
            smd = s->sm_arrays[mpm_list];
×
UNCOV
1494
        }
×
UNCOV
1495
        do {
×
UNCOV
1496
            switch (smd->type) {
×
UNCOV
1497
                case DETECT_CONTENT: {
×
UNCOV
1498
                    const DetectContentData *cd = (const DetectContentData *)smd->ctx;
×
UNCOV
1499
                    if (cd->flags & DETECT_CONTENT_MPM) {
×
UNCOV
1500
                        DumpContent(ctx.js, cd);
×
UNCOV
1501
                    }
×
UNCOV
1502
                    break;
×
1503
                }
×
UNCOV
1504
            }
×
1505

UNCOV
1506
            if (smd->is_last)
×
UNCOV
1507
                break;
×
UNCOV
1508
            smd++;
×
UNCOV
1509
        } while (1);
×
UNCOV
1510
        SCJbClose(ctx.js);
×
UNCOV
1511
    } else if (s->init_data->prefilter_sm) {
×
UNCOV
1512
        SCJbOpenObject(ctx.js, "prefilter");
×
UNCOV
1513
        int prefilter_list = SigMatchListSMBelongsTo(s, s->init_data->prefilter_sm);
×
UNCOV
1514
        const char *name;
×
UNCOV
1515
        if (prefilter_list < DETECT_SM_LIST_DYNAMIC_START)
×
UNCOV
1516
            name = DetectListToHumanString(prefilter_list);
×
1517
        else
×
1518
            name = DetectEngineBufferTypeGetNameById(de_ctx, prefilter_list);
×
UNCOV
1519
        SCJbSetString(ctx.js, "buffer", name);
×
UNCOV
1520
        const char *mname = sigmatch_table[s->init_data->prefilter_sm->type].name;
×
UNCOV
1521
        SCJbSetString(ctx.js, "name", mname);
×
UNCOV
1522
        SCJbClose(ctx.js);
×
UNCOV
1523
    }
×
1524

UNCOV
1525
    if (ctx.js_warnings) {
×
1526
        SCJbClose(ctx.js_warnings);
×
1527
        SCJbSetObject(ctx.js, "warnings", ctx.js_warnings);
×
1528
        SCJbFree(ctx.js_warnings);
×
1529
        ctx.js_warnings = NULL;
×
1530
    }
×
UNCOV
1531
    if (ctx.js_notes) {
×
UNCOV
1532
        SCJbClose(ctx.js_notes);
×
UNCOV
1533
        SCJbSetObject(ctx.js, "notes", ctx.js_notes);
×
UNCOV
1534
        SCJbFree(ctx.js_notes);
×
UNCOV
1535
        ctx.js_notes = NULL;
×
UNCOV
1536
    }
×
UNCOV
1537
    SCJbClose(ctx.js);
×
1538

UNCOV
1539
    const char *filename = "rules.json";
×
UNCOV
1540
    const char *log_dir = SCConfigGetLogDirectory();
×
UNCOV
1541
    char json_path[PATH_MAX] = "";
×
UNCOV
1542
    snprintf(json_path, sizeof(json_path), "%s/%s%s", log_dir,
×
UNCOV
1543
            de_ctx->ea->file_prefix ? de_ctx->ea->file_prefix : "", filename);
×
1544

UNCOV
1545
    SCMutexLock(&g_rules_analyzer_write_m);
×
UNCOV
1546
    FILE *fp = fopen(json_path, "a");
×
UNCOV
1547
    if (fp != NULL) {
×
UNCOV
1548
        fwrite(SCJbPtr(ctx.js), SCJbLen(ctx.js), 1, fp);
×
UNCOV
1549
        fprintf(fp, "\n");
×
UNCOV
1550
        fclose(fp);
×
UNCOV
1551
    }
×
UNCOV
1552
    SCMutexUnlock(&g_rules_analyzer_write_m);
×
UNCOV
1553
    SCJbFree(ctx.js);
×
UNCOV
1554
    SCReturn;
×
UNCOV
1555
}
×
1556

1557
void DumpPatterns(DetectEngineCtx *de_ctx)
1558
{
35,438✔
1559
    if (de_ctx->pattern_hash_table == NULL)
35,438✔
1560
        return;
35,438✔
1561

UNCOV
1562
    SCJsonBuilder *root_jb = SCJbNewObject();
×
UNCOV
1563
    SCJsonBuilder *arrays[de_ctx->buffer_type_id];
×
UNCOV
1564
    memset(&arrays, 0, sizeof(SCJsonBuilder *) * de_ctx->buffer_type_id);
×
1565

UNCOV
1566
    SCJbOpenArray(root_jb, "buffers");
×
1567

UNCOV
1568
    for (HashListTableBucket *htb = HashListTableGetListHead(de_ctx->pattern_hash_table);
×
UNCOV
1569
            htb != NULL; htb = HashListTableGetListNext(htb)) {
×
UNCOV
1570
        char str[1024] = "";
×
UNCOV
1571
        DetectPatternTracker *p = HashListTableGetListData(htb);
×
UNCOV
1572
        DetectContentPatternPrettyPrint(p->cd->content, p->cd->content_len, str, sizeof(str));
×
1573

UNCOV
1574
        SCJsonBuilder *jb = arrays[p->sm_list];
×
UNCOV
1575
        if (arrays[p->sm_list] == NULL) {
×
UNCOV
1576
            jb = arrays[p->sm_list] = SCJbNewObject();
×
UNCOV
1577
            const char *name;
×
UNCOV
1578
            if (p->sm_list < DETECT_SM_LIST_DYNAMIC_START)
×
UNCOV
1579
                name = DetectListToHumanString(p->sm_list);
×
UNCOV
1580
            else
×
UNCOV
1581
                name = DetectEngineBufferTypeGetNameById(de_ctx, p->sm_list);
×
UNCOV
1582
            SCJbSetString(jb, "name", name);
×
UNCOV
1583
            SCJbSetUint(jb, "list_id", p->sm_list);
×
1584

UNCOV
1585
            SCJbOpenArray(jb, "patterns");
×
UNCOV
1586
        }
×
1587

UNCOV
1588
        SCJbStartObject(jb);
×
UNCOV
1589
        SCJbSetString(jb, "pattern", str);
×
UNCOV
1590
        SCJbSetUint(jb, "patlen", p->cd->content_len);
×
UNCOV
1591
        SCJbSetUint(jb, "cnt", p->cnt);
×
UNCOV
1592
        SCJbSetUint(jb, "mpm", p->mpm);
×
UNCOV
1593
        SCJbOpenObject(jb, "flags");
×
UNCOV
1594
        SCJbSetBool(jb, "nocase", p->cd->flags & DETECT_CONTENT_NOCASE);
×
UNCOV
1595
        SCJbSetBool(jb, "negated", p->cd->flags & DETECT_CONTENT_NEGATED);
×
UNCOV
1596
        SCJbSetBool(jb, "depth", p->cd->flags & DETECT_CONTENT_DEPTH);
×
UNCOV
1597
        SCJbSetBool(jb, "offset", p->cd->flags & DETECT_CONTENT_OFFSET);
×
UNCOV
1598
        SCJbSetBool(jb, "endswith", p->cd->flags & DETECT_CONTENT_ENDS_WITH);
×
UNCOV
1599
        SCJbClose(jb);
×
UNCOV
1600
        SCJbClose(jb);
×
UNCOV
1601
    }
×
1602

UNCOV
1603
    for (uint32_t i = 0; i < de_ctx->buffer_type_id; i++) {
×
UNCOV
1604
        SCJsonBuilder *jb = arrays[i];
×
UNCOV
1605
        if (jb == NULL)
×
UNCOV
1606
            continue;
×
1607

UNCOV
1608
        SCJbClose(jb); // array
×
UNCOV
1609
        SCJbClose(jb); // object
×
1610

UNCOV
1611
        SCJbAppendObject(root_jb, jb);
×
UNCOV
1612
        SCJbFree(jb);
×
UNCOV
1613
    }
×
UNCOV
1614
    SCJbClose(root_jb);
×
UNCOV
1615
    SCJbClose(root_jb);
×
1616

UNCOV
1617
    const char *filename = "patterns.json";
×
UNCOV
1618
    const char *log_dir = SCConfigGetLogDirectory();
×
UNCOV
1619
    char json_path[PATH_MAX] = "";
×
UNCOV
1620
    snprintf(json_path, sizeof(json_path), "%s/%s%s", log_dir,
×
UNCOV
1621
            de_ctx->ea->file_prefix ? de_ctx->ea->file_prefix : "", filename);
×
1622

UNCOV
1623
    SCMutexLock(&g_rules_analyzer_write_m);
×
UNCOV
1624
    FILE *fp = fopen(json_path, "a");
×
UNCOV
1625
    if (fp != NULL) {
×
UNCOV
1626
        fwrite(SCJbPtr(root_jb), SCJbLen(root_jb), 1, fp);
×
UNCOV
1627
        fprintf(fp, "\n");
×
UNCOV
1628
        fclose(fp);
×
UNCOV
1629
    }
×
UNCOV
1630
    SCMutexUnlock(&g_rules_analyzer_write_m);
×
UNCOV
1631
    SCJbFree(root_jb);
×
1632

UNCOV
1633
    HashListTableFree(de_ctx->pattern_hash_table);
×
UNCOV
1634
    de_ctx->pattern_hash_table = NULL;
×
UNCOV
1635
}
×
1636

1637
static void EngineAnalysisItemsReset(EngineAnalysisCtx *ea_ctx)
UNCOV
1638
{
×
UNCOV
1639
    for (size_t i = 0; i < ARRAY_SIZE(analyzer_items); i++) {
×
UNCOV
1640
        ea_ctx->analyzer_items[i].item_seen = false;
×
UNCOV
1641
    }
×
UNCOV
1642
}
×
1643

1644
static void EngineAnalysisItemsInit(EngineAnalysisCtx *ea_ctx)
UNCOV
1645
{
×
UNCOV
1646
    if (ea_ctx->analyzer_initialized) {
×
UNCOV
1647
        EngineAnalysisItemsReset(ea_ctx);
×
UNCOV
1648
        return;
×
UNCOV
1649
    }
×
1650

UNCOV
1651
    ea_ctx->exposed_item_seen_list[0].bufname = "http_method";
×
UNCOV
1652
    ea_ctx->exposed_item_seen_list[1].bufname = "file_data";
×
UNCOV
1653
    ea_ctx->analyzer_items = SCCalloc(1, sizeof(analyzer_items));
×
UNCOV
1654
    if (!ea_ctx->analyzer_items) {
×
1655
        FatalError("Unable to allocate analysis scratch pad");
×
1656
    }
×
UNCOV
1657
    memset(ea_ctx->analyzer_item_map, -1, sizeof(ea_ctx->analyzer_item_map));
×
1658

UNCOV
1659
    for (size_t i = 0; i < ARRAY_SIZE(analyzer_items); i++) {
×
UNCOV
1660
        ea_ctx->analyzer_items[i] = analyzer_items[i];
×
UNCOV
1661
        DetectEngineAnalyzerItems *analyzer_item = &ea_ctx->analyzer_items[i];
×
1662

UNCOV
1663
        int item_id = DetectBufferTypeGetByName(analyzer_item->item_name);
×
UNCOV
1664
        DEBUG_VALIDATE_BUG_ON(item_id < 0 || item_id > UINT16_MAX);
×
UNCOV
1665
        analyzer_item->item_id = (uint16_t)item_id;
×
UNCOV
1666
        if (analyzer_item->item_id == -1) {
×
1667
            /* Mismatch between the analyzer_items array and what's supported */
1668
            FatalError("unable to initialize engine-analysis table: detect buffer \"%s\" not "
×
1669
                       "recognized.",
×
1670
                    analyzer_item->item_name);
×
1671
        }
×
UNCOV
1672
        analyzer_item->item_seen = false;
×
1673

UNCOV
1674
        if (analyzer_item->export_item_seen) {
×
UNCOV
1675
            for (size_t k = 0; k < ARRAY_SIZE(ea_ctx->exposed_item_seen_list); k++) {
×
UNCOV
1676
                if (0 ==
×
UNCOV
1677
                        strcmp(ea_ctx->exposed_item_seen_list[k].bufname, analyzer_item->item_name))
×
UNCOV
1678
                    ea_ctx->exposed_item_seen_list[k].item_seen_ptr = &analyzer_item->item_seen;
×
UNCOV
1679
            }
×
UNCOV
1680
        }
×
UNCOV
1681
        ea_ctx->analyzer_item_map[analyzer_item->item_id] = (int16_t)i;
×
UNCOV
1682
    }
×
1683

UNCOV
1684
    ea_ctx->analyzer_initialized = true;
×
UNCOV
1685
}
×
1686

1687
/**
1688
 * \brief Prints analysis of loaded rules.
1689
 *
1690
 *        Warns if potential rule issues are detected. For example,
1691
 *        warns if a rule uses a construct that may perform poorly,
1692
 *        e.g. pcre without content or with http_method content only;
1693
 *        warns if a rule uses a construct that may not be consistent with intent,
1694
 *        e.g. client side ports only, http and content without any http_* modifiers, etc.
1695
 *
1696
 * \param s Pointer to the signature.
1697
 */
1698
void EngineAnalysisRules(const DetectEngineCtx *de_ctx,
1699
        const Signature *s, const char *line)
UNCOV
1700
{
×
UNCOV
1701
    uint32_t rule_bidirectional = 0;
×
UNCOV
1702
    uint32_t rule_pcre = 0;
×
UNCOV
1703
    uint32_t rule_pcre_http = 0;
×
UNCOV
1704
    uint32_t rule_content = 0;
×
UNCOV
1705
    uint32_t rule_flow = 0;
×
UNCOV
1706
    uint32_t rule_flags = 0;
×
UNCOV
1707
    uint32_t rule_flow_toserver = 0;
×
UNCOV
1708
    uint32_t rule_flow_toclient = 0;
×
UNCOV
1709
    uint32_t rule_flow_nostream = 0;
×
UNCOV
1710
    uint32_t rule_ipv4_only = 0;
×
UNCOV
1711
    uint32_t rule_ipv6_only = 0;
×
UNCOV
1712
    uint32_t rule_flowbits = 0;
×
UNCOV
1713
    uint32_t rule_flowint = 0;
×
UNCOV
1714
    uint32_t rule_content_http = 0;
×
UNCOV
1715
    uint32_t rule_content_offset_depth = 0;
×
UNCOV
1716
    int32_t list_id = 0;
×
UNCOV
1717
    uint32_t rule_warning = 0;
×
UNCOV
1718
    uint32_t stream_buf = 0;
×
UNCOV
1719
    uint32_t packet_buf = 0;
×
UNCOV
1720
    uint32_t file_store = 0;
×
UNCOV
1721
    uint32_t warn_pcre_no_content = 0;
×
UNCOV
1722
    uint32_t warn_pcre_http_content = 0;
×
UNCOV
1723
    uint32_t warn_pcre_http = 0;
×
UNCOV
1724
    uint32_t warn_content_http_content = 0;
×
UNCOV
1725
    uint32_t warn_content_http = 0;
×
UNCOV
1726
    uint32_t warn_tcp_no_flow = 0;
×
UNCOV
1727
    uint32_t warn_client_ports = 0;
×
UNCOV
1728
    uint32_t warn_direction = 0;
×
UNCOV
1729
    uint32_t warn_method_toclient = 0;
×
UNCOV
1730
    uint32_t warn_method_serverbody = 0;
×
UNCOV
1731
    uint32_t warn_pcre_method = 0;
×
UNCOV
1732
    uint32_t warn_encoding_norm_http_buf = 0;
×
UNCOV
1733
    uint32_t warn_file_store_not_present = 0;
×
UNCOV
1734
    uint32_t warn_offset_depth_pkt_stream = 0;
×
UNCOV
1735
    uint32_t warn_offset_depth_alproto = 0;
×
UNCOV
1736
    uint32_t warn_non_alproto_fp_for_alproto_sig = 0;
×
UNCOV
1737
    uint32_t warn_no_direction = 0;
×
UNCOV
1738
    uint32_t warn_both_direction = 0;
×
1739

UNCOV
1740
    EngineAnalysisItemsInit(de_ctx->ea);
×
1741

UNCOV
1742
    bool *http_method_item_seen_ptr = de_ctx->ea->exposed_item_seen_list[0].item_seen_ptr;
×
UNCOV
1743
    bool *http_server_body_item_seen_ptr = de_ctx->ea->exposed_item_seen_list[1].item_seen_ptr;
×
1744

UNCOV
1745
    if (s->init_data->init_flags & SIG_FLAG_INIT_BIDIREC) {
×
1746
        rule_bidirectional = 1;
×
1747
    }
×
1748

UNCOV
1749
    if (s->flags & SIG_FLAG_REQUIRE_PACKET) {
×
UNCOV
1750
        packet_buf += 1;
×
UNCOV
1751
    }
×
UNCOV
1752
    if (s->flags & SIG_FLAG_FILESTORE) {
×
UNCOV
1753
        file_store += 1;
×
UNCOV
1754
    }
×
UNCOV
1755
    if (s->flags & SIG_FLAG_REQUIRE_STREAM) {
×
UNCOV
1756
        stream_buf += 1;
×
UNCOV
1757
    }
×
1758

UNCOV
1759
    if (s->proto.flags & DETECT_PROTO_IPV4) {
×
1760
        rule_ipv4_only += 1;
×
1761
    }
×
UNCOV
1762
    if (s->proto.flags & DETECT_PROTO_IPV6) {
×
1763
        rule_ipv6_only += 1;
×
1764
    }
×
1765

UNCOV
1766
    for (list_id = 0; list_id < DETECT_SM_LIST_MAX; list_id++) {
×
UNCOV
1767
        SigMatch *sm = NULL;
×
UNCOV
1768
        for (sm = s->init_data->smlists[list_id]; sm != NULL; sm = sm->next) {
×
UNCOV
1769
            int16_t item_slot = de_ctx->ea->analyzer_item_map[list_id];
×
UNCOV
1770
            if (sm->type == DETECT_PCRE) {
×
UNCOV
1771
                if (item_slot == -1) {
×
UNCOV
1772
                    rule_pcre++;
×
UNCOV
1773
                    continue;
×
UNCOV
1774
                }
×
1775

1776
                rule_pcre_http++;
×
1777
                de_ctx->ea->analyzer_items[item_slot].item_seen = true;
×
UNCOV
1778
            } else if (sm->type == DETECT_CONTENT) {
×
UNCOV
1779
                if (item_slot == -1) {
×
UNCOV
1780
                    rule_content++;
×
UNCOV
1781
                    if (list_id == DETECT_SM_LIST_PMATCH) {
×
UNCOV
1782
                        DetectContentData *cd = (DetectContentData *)sm->ctx;
×
UNCOV
1783
                        if (cd->flags & (DETECT_CONTENT_OFFSET | DETECT_CONTENT_DEPTH)) {
×
UNCOV
1784
                            rule_content_offset_depth++;
×
UNCOV
1785
                        }
×
UNCOV
1786
                    }
×
UNCOV
1787
                    continue;
×
UNCOV
1788
                }
×
1789

1790
                rule_content_http++;
×
1791
                de_ctx->ea->analyzer_items[item_slot].item_seen = true;
×
1792

1793
                if (de_ctx->ea->analyzer_items[item_slot].check_encoding_match) {
×
1794
                    DetectContentData *cd = (DetectContentData *)sm->ctx;
×
1795
                    if (cd != NULL &&
×
1796
                            PerCentEncodingMatch(de_ctx->ea, cd->content, cd->content_len) > 0) {
×
1797
                        warn_encoding_norm_http_buf += 1;
×
1798
                    }
×
1799
                }
×
1800
            }
×
UNCOV
1801
            else if (sm->type == DETECT_FLOW) {
×
UNCOV
1802
                rule_flow += 1;
×
UNCOV
1803
                if ((s->flags & SIG_FLAG_TOSERVER) && !(s->flags & SIG_FLAG_TOCLIENT)) {
×
UNCOV
1804
                    rule_flow_toserver = 1;
×
UNCOV
1805
                }
×
UNCOV
1806
                else if ((s->flags & SIG_FLAG_TOCLIENT) && !(s->flags & SIG_FLAG_TOSERVER)) {
×
1807
                    rule_flow_toclient = 1;
×
1808
                }
×
UNCOV
1809
                DetectFlowData *fd = (DetectFlowData *)sm->ctx;
×
UNCOV
1810
                if (fd != NULL) {
×
UNCOV
1811
                    if (fd->flags & DETECT_FLOW_FLAG_NOSTREAM)
×
1812
                        rule_flow_nostream = 1;
×
UNCOV
1813
                }
×
UNCOV
1814
            }
×
UNCOV
1815
            else if (sm->type == DETECT_FLOWBITS) {
×
UNCOV
1816
                if (list_id == DETECT_SM_LIST_MATCH) {
×
UNCOV
1817
                    rule_flowbits += 1;
×
UNCOV
1818
                }
×
UNCOV
1819
            }
×
UNCOV
1820
            else if (sm->type == DETECT_FLOWINT) {
×
UNCOV
1821
                if (list_id == DETECT_SM_LIST_MATCH) {
×
UNCOV
1822
                    rule_flowint += 1;
×
UNCOV
1823
                }
×
UNCOV
1824
            }
×
UNCOV
1825
            else if (sm->type == DETECT_FLAGS) {
×
UNCOV
1826
                if (sm->ctx != NULL) {
×
UNCOV
1827
                    rule_flags = 1;
×
UNCOV
1828
                }
×
UNCOV
1829
            }
×
UNCOV
1830
        } /* for (sm = s->init_data->smlists[list_id]; sm != NULL; sm = sm->next) */
×
1831

UNCOV
1832
    } /* for ( ; list_id < DETECT_SM_LIST_MAX; list_id++) */
×
1833

UNCOV
1834
    if (file_store && !SCRequiresFeature("output::file-store")) {
×
UNCOV
1835
        rule_warning += 1;
×
UNCOV
1836
        warn_file_store_not_present = 1;
×
UNCOV
1837
    }
×
1838

UNCOV
1839
    if (rule_pcre > 0 && rule_content == 0 && rule_content_http == 0) {
×
UNCOV
1840
        rule_warning += 1;
×
UNCOV
1841
        warn_pcre_no_content = 1;
×
UNCOV
1842
    }
×
1843

UNCOV
1844
    if (rule_content_http > 0 && rule_pcre > 0 && rule_pcre_http == 0) {
×
1845
        rule_warning += 1;
×
1846
        warn_pcre_http_content = 1;
×
UNCOV
1847
    } else if (s->alproto == ALPROTO_HTTP1 && rule_pcre > 0 && rule_pcre_http == 0) {
×
1848
        rule_warning += 1;
×
1849
        warn_pcre_http = 1;
×
1850
    }
×
1851

UNCOV
1852
    if (rule_content > 0 && rule_content_http > 0) {
×
1853
        rule_warning += 1;
×
1854
        warn_content_http_content = 1;
×
1855
    }
×
UNCOV
1856
    if (s->alproto == ALPROTO_HTTP1 && rule_content > 0 && rule_content_http == 0) {
×
UNCOV
1857
        rule_warning += 1;
×
UNCOV
1858
        warn_content_http = 1;
×
UNCOV
1859
    }
×
UNCOV
1860
    if (rule_content == 1) {
×
1861
         //todo: warning if content is weak, separate warning for pcre + weak content
UNCOV
1862
    }
×
UNCOV
1863
    if (rule_flow == 0 && rule_flags == 0 && !(s->proto.flags & DETECT_PROTO_ANY) &&
×
UNCOV
1864
            DetectProtoContainsProto(&s->proto, IPPROTO_TCP) &&
×
UNCOV
1865
            (rule_content || rule_content_http || rule_pcre || rule_pcre_http || rule_flowbits ||
×
UNCOV
1866
                    rule_flowint)) {
×
UNCOV
1867
        rule_warning += 1;
×
UNCOV
1868
        warn_tcp_no_flow = 1;
×
UNCOV
1869
    }
×
UNCOV
1870
    if (rule_flow && !rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)
×
UNCOV
1871
                  && !((s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY))) {
×
UNCOV
1872
        if (((s->flags & SIG_FLAG_TOSERVER) && !(s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY))
×
UNCOV
1873
          || ((s->flags & SIG_FLAG_TOCLIENT) && !(s->flags & SIG_FLAG_DP_ANY) && (s->flags & SIG_FLAG_SP_ANY))) {
×
1874
            rule_warning += 1;
×
1875
            warn_client_ports = 1;
×
1876
        }
×
UNCOV
1877
    }
×
UNCOV
1878
    if (rule_flow && rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)) {
×
1879
        rule_warning += 1;
×
1880
        warn_direction = 1;
×
1881
    }
×
1882

UNCOV
1883
    if (*http_method_item_seen_ptr) {
×
1884
        if (rule_flow && rule_flow_toclient) {
×
1885
            rule_warning += 1;
×
1886
            warn_method_toclient = 1;
×
1887
        }
×
1888
        if (*http_server_body_item_seen_ptr) {
×
1889
            rule_warning += 1;
×
1890
            warn_method_serverbody = 1;
×
1891
        }
×
1892
        if (rule_content == 0 && rule_content_http == 0 && (rule_pcre > 0 || rule_pcre_http > 0)) {
×
1893
            rule_warning += 1;
×
1894
            warn_pcre_method = 1;
×
1895
        }
×
1896
    }
×
UNCOV
1897
    if (rule_content_offset_depth > 0 && stream_buf && packet_buf) {
×
UNCOV
1898
        rule_warning += 1;
×
UNCOV
1899
        warn_offset_depth_pkt_stream = 1;
×
UNCOV
1900
    }
×
UNCOV
1901
    if (rule_content_offset_depth > 0 && !stream_buf && packet_buf && s->alproto != ALPROTO_UNKNOWN) {
×
1902
        rule_warning += 1;
×
1903
        warn_offset_depth_alproto = 1;
×
1904
    }
×
UNCOV
1905
    if (s->init_data->mpm_sm != NULL && s->alproto == ALPROTO_HTTP1 &&
×
UNCOV
1906
            s->init_data->mpm_sm_list == DETECT_SM_LIST_PMATCH) {
×
UNCOV
1907
        rule_warning += 1;
×
UNCOV
1908
        warn_non_alproto_fp_for_alproto_sig = 1;
×
UNCOV
1909
    }
×
1910

UNCOV
1911
    if ((s->flags & (SIG_FLAG_TOSERVER|SIG_FLAG_TOCLIENT)) == 0) {
×
1912
        warn_no_direction += 1;
×
1913
        rule_warning += 1;
×
1914
    }
×
1915

1916
    /* No warning about direction for ICMP protos */
UNCOV
1917
    if (!(DetectProtoContainsProto(&s->proto, IPPROTO_ICMPV6) && DetectProtoContainsProto(&s->proto, IPPROTO_ICMP))) {
×
UNCOV
1918
        if ((s->flags & (SIG_FLAG_TOSERVER|SIG_FLAG_TOCLIENT)) == (SIG_FLAG_TOSERVER|SIG_FLAG_TOCLIENT)) {
×
UNCOV
1919
            warn_both_direction += 1;
×
UNCOV
1920
            rule_warning += 1;
×
UNCOV
1921
        }
×
UNCOV
1922
    }
×
1923

UNCOV
1924
    if (!rule_warnings_only || (rule_warnings_only && rule_warning > 0)) {
×
UNCOV
1925
        FILE *fp = de_ctx->ea->rule_engine_analysis_fp;
×
UNCOV
1926
        fprintf(fp, "== Sid: %u ==\n", s->id);
×
UNCOV
1927
        fprintf(fp, "%s\n", line);
×
1928

UNCOV
1929
        switch (s->type) {
×
1930
            case SIG_TYPE_NOT_SET:
×
1931
                break;
×
UNCOV
1932
            case SIG_TYPE_IPONLY:
×
UNCOV
1933
                fprintf(fp, "    Rule is ip only.\n");
×
UNCOV
1934
                break;
×
UNCOV
1935
            case SIG_TYPE_LIKE_IPONLY:
×
UNCOV
1936
                fprintf(fp, "    Rule is like ip only.\n");
×
UNCOV
1937
                break;
×
UNCOV
1938
            case SIG_TYPE_PDONLY:
×
UNCOV
1939
                fprintf(fp, "    Rule is PD only.\n");
×
UNCOV
1940
                break;
×
UNCOV
1941
            case SIG_TYPE_DEONLY:
×
UNCOV
1942
                fprintf(fp, "    Rule is DE only.\n");
×
UNCOV
1943
                break;
×
UNCOV
1944
            case SIG_TYPE_PKT:
×
UNCOV
1945
                fprintf(fp, "    Rule is packet inspecting.\n");
×
UNCOV
1946
                break;
×
UNCOV
1947
            case SIG_TYPE_PKT_STREAM:
×
UNCOV
1948
                fprintf(fp, "    Rule is packet and stream inspecting.\n");
×
UNCOV
1949
                break;
×
UNCOV
1950
            case SIG_TYPE_STREAM:
×
UNCOV
1951
                fprintf(fp, "    Rule is stream inspecting.\n");
×
UNCOV
1952
                break;
×
UNCOV
1953
            case SIG_TYPE_APPLAYER:
×
UNCOV
1954
                fprintf(fp, "    Rule is app-layer inspecting.\n");
×
UNCOV
1955
                break;
×
UNCOV
1956
            case SIG_TYPE_APP_TX:
×
UNCOV
1957
                fprintf(fp, "    Rule is App-layer TX inspecting.\n");
×
UNCOV
1958
                break;
×
1959
            case SIG_TYPE_MAX:
×
1960
                break;
×
UNCOV
1961
        }
×
UNCOV
1962
        if (rule_ipv6_only)
×
1963
            fprintf(fp, "    Rule is IPv6 only.\n");
×
UNCOV
1964
        if (rule_ipv4_only)
×
1965
            fprintf(fp, "    Rule is IPv4 only.\n");
×
UNCOV
1966
        if (packet_buf)
×
UNCOV
1967
            fprintf(fp, "    Rule matches on packets.\n");
×
UNCOV
1968
        if (!rule_flow_nostream && stream_buf &&
×
UNCOV
1969
                (rule_flow || rule_flowbits || rule_flowint || rule_content || rule_pcre)) {
×
UNCOV
1970
            fprintf(fp, "    Rule matches on reassembled stream.\n");
×
UNCOV
1971
        }
×
UNCOV
1972
        for(size_t i = 0; i < ARRAY_SIZE(analyzer_items); i++) {
×
UNCOV
1973
            DetectEngineAnalyzerItems *ai = &de_ctx->ea->analyzer_items[i];
×
UNCOV
1974
            if (ai->item_seen) {
×
1975
                fprintf(fp, "    Rule matches on %s buffer.\n", ai->display_name);
×
1976
            }
×
UNCOV
1977
        }
×
UNCOV
1978
        if (s->alproto != ALPROTO_UNKNOWN) {
×
UNCOV
1979
            fprintf(fp, "    App layer protocol is %s.\n", AppProtoToString(s->alproto));
×
UNCOV
1980
        }
×
UNCOV
1981
        if (rule_content || rule_content_http || rule_pcre || rule_pcre_http) {
×
UNCOV
1982
            fprintf(fp,
×
UNCOV
1983
                    "    Rule contains %u content options, %u http content options, %u pcre "
×
UNCOV
1984
                    "options, and %u pcre options with http modifiers.\n",
×
UNCOV
1985
                    rule_content, rule_content_http, rule_pcre, rule_pcre_http);
×
UNCOV
1986
        }
×
1987

1988
        /* print fast pattern info */
UNCOV
1989
        if (s->init_data->prefilter_sm) {
×
UNCOV
1990
            fprintf(fp, "    Prefilter on: %s.\n",
×
UNCOV
1991
                    sigmatch_table[s->init_data->prefilter_sm->type].name);
×
UNCOV
1992
        } else {
×
UNCOV
1993
            EngineAnalysisRulesPrintFP(de_ctx, s);
×
UNCOV
1994
        }
×
1995

1996
        /* this is where the warnings start */
UNCOV
1997
        if (warn_pcre_no_content /*rule_pcre > 0 && rule_content == 0 && rule_content_http == 0*/) {
×
UNCOV
1998
            fprintf(fp, "    Warning: Rule uses pcre without a content option present.\n"
×
UNCOV
1999
                        "             -Consider adding a content to improve performance of this "
×
UNCOV
2000
                        "rule.\n");
×
UNCOV
2001
        }
×
UNCOV
2002
        if (warn_pcre_http_content /*rule_content_http > 0 && rule_pcre > 0 && rule_pcre_http == 0*/) {
×
2003
            fprintf(fp, "    Warning: Rule uses content options with http_* and pcre options "
×
2004
                        "without http modifiers.\n"
×
2005
                        "             -Consider adding http pcre modifier.\n");
×
2006
        }
×
UNCOV
2007
        else if (warn_pcre_http /*s->alproto == ALPROTO_HTTP1 && rule_pcre > 0 && rule_pcre_http == 0*/) {
×
2008
            fprintf(fp, "    Warning: Rule app layer protocol is http, but pcre options do not "
×
2009
                        "have http modifiers.\n"
×
2010
                        "             -Consider adding http pcre modifiers.\n");
×
2011
        }
×
UNCOV
2012
        if (warn_content_http_content /*rule_content > 0 && rule_content_http > 0*/) {
×
2013
            fprintf(fp,
×
2014
                    "    Warning: Rule contains content with http_* and content without http_*.\n"
×
2015
                    "             -Consider adding http content modifiers.\n");
×
2016
        }
×
UNCOV
2017
        if (warn_content_http /*s->alproto == ALPROTO_HTTP1 && rule_content > 0 && rule_content_http == 0*/) {
×
UNCOV
2018
            fprintf(fp, "    Warning: Rule app layer protocol is http, but content options do not "
×
UNCOV
2019
                        "have http_* modifiers.\n"
×
UNCOV
2020
                        "             -Consider adding http content modifiers.\n");
×
UNCOV
2021
        }
×
UNCOV
2022
        if (rule_content == 1) {
×
2023
             //todo: warning if content is weak, separate warning for pcre + weak content
UNCOV
2024
        }
×
UNCOV
2025
        if (warn_encoding_norm_http_buf) {
×
2026
            fprintf(fp, "    Warning: Rule may contain percent encoded content for a normalized "
×
2027
                        "http buffer match.\n");
×
2028
        }
×
UNCOV
2029
        if (warn_tcp_no_flow /*rule_flow == 0 && rule_flags == 0
×
UNCOV
2030
                && !(s->proto.flags & DETECT_PROTO_ANY) && DetectProtoContainsProto(&s->proto, IPPROTO_TCP)*/) {
×
UNCOV
2031
            fprintf(fp, "    Warning: TCP rule without a flow or flags option.\n"
×
UNCOV
2032
                        "             -Consider adding flow or flags to improve performance of "
×
UNCOV
2033
                        "this rule.\n");
×
UNCOV
2034
        }
×
UNCOV
2035
        if (warn_client_ports /*rule_flow && !rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)
×
2036
                      && !((s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY)))
2037
            if (((s->flags & SIG_FLAG_TOSERVER) && !(s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY))
UNCOV
2038
                || ((s->flags & SIG_FLAG_TOCLIENT) && !(s->flags & SIG_FLAG_DP_ANY) && (s->flags & SIG_FLAG_SP_ANY))*/) {
×
2039
            fprintf(fp,
×
2040
                    "    Warning: Rule contains ports or port variables only on the client side.\n"
×
2041
                    "             -Flow direction possibly inconsistent with rule.\n");
×
2042
        }
×
UNCOV
2043
        if (warn_direction /*rule_flow && rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)*/) {
×
2044
            fprintf(fp, "    Warning: Rule is bidirectional and has a flow option with a specific "
×
2045
                        "direction.\n");
×
2046
        }
×
UNCOV
2047
        if (warn_method_toclient /*http_method_buf && rule_flow && rule_flow_toclient*/) {
×
2048
            fprintf(fp, "    Warning: Rule uses content or pcre for http_method with "
×
2049
                        "flow:to_client or from_server\n");
×
2050
        }
×
UNCOV
2051
        if (warn_method_serverbody /*http_method_buf && http_server_body_buf*/) {
×
2052
            fprintf(fp, "    Warning: Rule uses content or pcre for http_method with content or "
×
2053
                        "pcre for http_server_body.\n");
×
2054
        }
×
UNCOV
2055
        if (warn_pcre_method /*http_method_buf && rule_content == 0 && rule_content_http == 0
×
UNCOV
2056
                               && (rule_pcre > 0 || rule_pcre_http > 0)*/) {
×
2057
            fprintf(fp, "    Warning: Rule uses pcre with only a http_method content; possible "
×
2058
                        "performance issue.\n");
×
2059
        }
×
UNCOV
2060
        if (warn_offset_depth_pkt_stream) {
×
UNCOV
2061
            fprintf(fp, "    Warning: Rule has depth"
×
UNCOV
2062
                        "/offset with raw content keywords.  Please note the "
×
UNCOV
2063
                        "offset/depth will be checked against both packet "
×
UNCOV
2064
                        "payloads and stream.  If you meant to have the offset/"
×
UNCOV
2065
                        "depth checked against just the payload, you can update "
×
UNCOV
2066
                        "the signature as \"alert tcp-pkt...\"\n");
×
UNCOV
2067
        }
×
UNCOV
2068
        if (warn_offset_depth_alproto) {
×
2069
            fprintf(fp,
×
2070
                    "    Warning: Rule has "
×
2071
                    "offset/depth set along with a match on a specific "
×
2072
                    "app layer protocol - %d.  This can lead to FNs if we "
×
2073
                    "have a offset/depth content match on a packet payload "
×
2074
                    "before we can detect the app layer protocol for the "
×
2075
                    "flow.\n",
×
2076
                    s->alproto);
×
2077
        }
×
UNCOV
2078
        if (warn_non_alproto_fp_for_alproto_sig) {
×
UNCOV
2079
            fprintf(fp, "    Warning: Rule app layer "
×
UNCOV
2080
                        "protocol is http, but the fast_pattern is set on the raw "
×
UNCOV
2081
                        "stream.  Consider adding fast_pattern over a http "
×
UNCOV
2082
                        "buffer for increased performance.");
×
UNCOV
2083
        }
×
UNCOV
2084
        if (warn_no_direction) {
×
2085
            fprintf(fp, "    Warning: Rule has no direction indicator.\n");
×
2086
        }
×
UNCOV
2087
        if (warn_both_direction) {
×
UNCOV
2088
            fprintf(fp, "    Warning: Rule is inspecting both the request and the response.\n");
×
UNCOV
2089
        }
×
UNCOV
2090
        if (warn_file_store_not_present) {
×
UNCOV
2091
            fprintf(fp, "    Warning: Rule requires file-store but the output file-store is not "
×
UNCOV
2092
                        "enabled.\n");
×
UNCOV
2093
        }
×
UNCOV
2094
        if (rule_warning == 0) {
×
UNCOV
2095
            fprintf(fp, "    No warnings for this rule.\n");
×
UNCOV
2096
        }
×
UNCOV
2097
        fprintf(fp, "\n");
×
UNCOV
2098
    }
×
UNCOV
2099
}
×
2100

2101
#include "app-layer-parser.h"
2102

2103
static void FirewallAddRulesForState(const DetectEngineCtx *de_ctx, const AppProto a,
2104
        const uint8_t state, const uint8_t direction, RuleAnalyzer *ctx)
UNCOV
2105
{
×
UNCOV
2106
    uint32_t accept_rules = 0;
×
UNCOV
2107
    SCJbSetString(ctx->js, "policy", "drop:flow");
×
UNCOV
2108
    SCJbOpenArray(ctx->js, "rules");
×
UNCOV
2109
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2110
        if ((s->flags & SIG_FLAG_FIREWALL) == 0)
×
UNCOV
2111
            break;
×
UNCOV
2112
        if (s->type != SIG_TYPE_APP_TX)
×
UNCOV
2113
            continue;
×
UNCOV
2114
        if (s->alproto != a)
×
UNCOV
2115
            continue;
×
2116

UNCOV
2117
        if (direction == STREAM_TOSERVER) {
×
UNCOV
2118
            if (s->flags & SIG_FLAG_TOCLIENT) {
×
UNCOV
2119
                continue;
×
UNCOV
2120
            }
×
UNCOV
2121
        } else {
×
UNCOV
2122
            if (s->flags & SIG_FLAG_TOSERVER) {
×
UNCOV
2123
                continue;
×
UNCOV
2124
            }
×
UNCOV
2125
        }
×
2126

UNCOV
2127
        if (s->app_progress_hook == state) {
×
UNCOV
2128
            SCJbAppendString(ctx->js, s->sig_str);
×
UNCOV
2129
            accept_rules += ((s->action & ACTION_ACCEPT) != 0);
×
UNCOV
2130
        }
×
UNCOV
2131
    }
×
UNCOV
2132
    SCJbClose(ctx->js);
×
2133

UNCOV
2134
    if (accept_rules == 0) {
×
UNCOV
2135
        AnalyzerWarning(ctx, (char *)"no accept rules for state, default policy will be applied");
×
UNCOV
2136
    }
×
UNCOV
2137
}
×
2138

2139
int FirewallAnalyzer(const DetectEngineCtx *de_ctx)
UNCOV
2140
{
×
UNCOV
2141
    RuleAnalyzer ctx = { NULL, NULL, NULL };
×
UNCOV
2142
    ctx.js = SCJbNewObject();
×
UNCOV
2143
    if (ctx.js == NULL)
×
2144
        return -1;
×
2145

UNCOV
2146
    SCJbOpenObject(ctx.js, "tables");
×
UNCOV
2147
    SCJbOpenObject(ctx.js, "packet:filter");
×
UNCOV
2148
    SCJbSetString(ctx.js, "policy", "drop:packet");
×
UNCOV
2149
    SCJbOpenArray(ctx.js, "rules");
×
UNCOV
2150
    uint32_t accept_rules = 0;
×
UNCOV
2151
    uint32_t last_sid = 0;
×
UNCOV
2152
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2153
        if ((s->flags & SIG_FLAG_FIREWALL) == 0)
×
UNCOV
2154
            break;
×
UNCOV
2155
        if (s->type != SIG_TYPE_PKT)
×
UNCOV
2156
            continue;
×
2157
        /* don't double list <> sigs */
UNCOV
2158
        if (last_sid == s->id)
×
UNCOV
2159
            continue;
×
UNCOV
2160
        last_sid = s->id;
×
UNCOV
2161
        SCJbAppendString(ctx.js, s->sig_str);
×
UNCOV
2162
        accept_rules += ((s->action & ACTION_ACCEPT) != 0);
×
UNCOV
2163
    }
×
UNCOV
2164
    SCJbClose(ctx.js);
×
UNCOV
2165
    if (accept_rules == 0) {
×
UNCOV
2166
        AnalyzerWarning(&ctx,
×
UNCOV
2167
                (char *)"no accept rules for \'packet:filter\', default policy will be applied");
×
UNCOV
2168
    }
×
UNCOV
2169
    if (ctx.js_warnings) {
×
UNCOV
2170
        SCJbClose(ctx.js_warnings);
×
UNCOV
2171
        SCJbSetObject(ctx.js, "warnings", ctx.js_warnings);
×
UNCOV
2172
        SCJbFree(ctx.js_warnings);
×
UNCOV
2173
        ctx.js_warnings = NULL;
×
UNCOV
2174
    }
×
UNCOV
2175
    SCJbClose(ctx.js); // packet_filter
×
2176

UNCOV
2177
    for (AppProto a = 0; a < g_alproto_max; a++) {
×
UNCOV
2178
        if (!AppProtoIsValid(a))
×
UNCOV
2179
            continue;
×
2180

2181
        // HACK not all protocols have named states yet
UNCOV
2182
        const char *hack = AppLayerParserGetStateNameById(IPPROTO_TCP, a, 0, STREAM_TOSERVER);
×
UNCOV
2183
        if (!hack)
×
UNCOV
2184
            continue;
×
2185

UNCOV
2186
        SCJbOpenObject(ctx.js, AppProtoToString(a));
×
UNCOV
2187
        const uint8_t complete_state_ts =
×
UNCOV
2188
                (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER);
×
UNCOV
2189
        for (uint8_t state = 0; state < complete_state_ts; state++) {
×
UNCOV
2190
            const char *name =
×
UNCOV
2191
                    AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER);
×
UNCOV
2192
            char table_name[128];
×
UNCOV
2193
            snprintf(table_name, sizeof(table_name), "app:%s:%s", AppProtoToString(a), name);
×
UNCOV
2194
            SCJbOpenObject(ctx.js, table_name);
×
UNCOV
2195
            FirewallAddRulesForState(de_ctx, a, state, STREAM_TOSERVER, &ctx);
×
UNCOV
2196
            if (ctx.js_warnings) {
×
UNCOV
2197
                SCJbClose(ctx.js_warnings);
×
UNCOV
2198
                SCJbSetObject(ctx.js, "warnings", ctx.js_warnings);
×
UNCOV
2199
                SCJbFree(ctx.js_warnings);
×
UNCOV
2200
                ctx.js_warnings = NULL;
×
UNCOV
2201
            }
×
UNCOV
2202
            SCJbClose(ctx.js);
×
UNCOV
2203
        }
×
UNCOV
2204
        const uint8_t complete_state_tc =
×
UNCOV
2205
                (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT);
×
UNCOV
2206
        for (uint8_t state = 0; state < complete_state_tc; state++) {
×
UNCOV
2207
            const char *name =
×
UNCOV
2208
                    AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT);
×
UNCOV
2209
            char table_name[128];
×
UNCOV
2210
            snprintf(table_name, sizeof(table_name), "app:%s:%s", AppProtoToString(a), name);
×
UNCOV
2211
            SCJbOpenObject(ctx.js, table_name);
×
UNCOV
2212
            FirewallAddRulesForState(de_ctx, a, state, STREAM_TOCLIENT, &ctx);
×
UNCOV
2213
            if (ctx.js_warnings) {
×
UNCOV
2214
                SCJbClose(ctx.js_warnings);
×
UNCOV
2215
                SCJbSetObject(ctx.js, "warnings", ctx.js_warnings);
×
UNCOV
2216
                SCJbFree(ctx.js_warnings);
×
UNCOV
2217
                ctx.js_warnings = NULL;
×
UNCOV
2218
            }
×
UNCOV
2219
            SCJbClose(ctx.js);
×
UNCOV
2220
        }
×
UNCOV
2221
        SCJbClose(ctx.js); // app layer
×
UNCOV
2222
    }
×
UNCOV
2223
    SCJbOpenObject(ctx.js, "packet:td");
×
UNCOV
2224
    SCJbSetString(ctx.js, "policy", "accept:hook");
×
UNCOV
2225
    last_sid = 0;
×
UNCOV
2226
    SCJbOpenArray(ctx.js, "rules");
×
UNCOV
2227
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2228
        if ((s->flags & SIG_FLAG_FIREWALL) != 0)
×
UNCOV
2229
            continue;
×
UNCOV
2230
        if (s->type == SIG_TYPE_APP_TX)
×
UNCOV
2231
            continue;
×
UNCOV
2232
        if (last_sid == s->id)
×
2233
            continue;
×
UNCOV
2234
        last_sid = s->id;
×
UNCOV
2235
        SCJbAppendString(ctx.js, s->sig_str);
×
UNCOV
2236
    }
×
UNCOV
2237
    SCJbClose(ctx.js); // rules
×
UNCOV
2238
    SCJbClose(ctx.js); // packet:td
×
UNCOV
2239
    SCJbOpenObject(ctx.js, "app:td");
×
UNCOV
2240
    SCJbSetString(ctx.js, "policy", "accept:hook");
×
UNCOV
2241
    last_sid = 0;
×
UNCOV
2242
    SCJbOpenArray(ctx.js, "rules");
×
UNCOV
2243
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2244
        if ((s->flags & SIG_FLAG_FIREWALL) != 0)
×
UNCOV
2245
            continue;
×
UNCOV
2246
        if (s->type != SIG_TYPE_APP_TX)
×
UNCOV
2247
            continue;
×
UNCOV
2248
        if (last_sid == s->id)
×
2249
            continue;
×
UNCOV
2250
        last_sid = s->id;
×
UNCOV
2251
        SCJbAppendString(ctx.js, s->sig_str);
×
UNCOV
2252
    }
×
UNCOV
2253
    SCJbClose(ctx.js); // rules
×
UNCOV
2254
    SCJbClose(ctx.js); // app:td
×
UNCOV
2255
    SCJbClose(ctx.js); // tables
×
2256

UNCOV
2257
    SCJbOpenObject(ctx.js, "lists");
×
UNCOV
2258
    SCJbOpenObject(ctx.js, "firewall");
×
UNCOV
2259
    last_sid = 0;
×
UNCOV
2260
    SCJbOpenArray(ctx.js, "rules");
×
UNCOV
2261
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2262
        if ((s->flags & SIG_FLAG_FIREWALL) == 0)
×
UNCOV
2263
            continue;
×
UNCOV
2264
        if (last_sid == s->id)
×
UNCOV
2265
            continue;
×
UNCOV
2266
        last_sid = s->id;
×
UNCOV
2267
        SCJbAppendString(ctx.js, s->sig_str);
×
UNCOV
2268
    }
×
UNCOV
2269
    SCJbClose(ctx.js); // rules
×
UNCOV
2270
    SCJbClose(ctx.js); // firewall
×
2271

UNCOV
2272
    SCJbOpenObject(ctx.js, "td");
×
UNCOV
2273
    last_sid = 0;
×
UNCOV
2274
    SCJbOpenArray(ctx.js, "rules");
×
UNCOV
2275
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2276
        if ((s->flags & SIG_FLAG_FIREWALL) != 0)
×
UNCOV
2277
            continue;
×
UNCOV
2278
        if (last_sid == s->id)
×
2279
            continue;
×
UNCOV
2280
        last_sid = s->id;
×
UNCOV
2281
        SCJbAppendString(ctx.js, s->sig_str);
×
UNCOV
2282
    }
×
UNCOV
2283
    SCJbClose(ctx.js); // rules
×
UNCOV
2284
    SCJbClose(ctx.js); // td
×
2285

UNCOV
2286
    SCJbOpenObject(ctx.js, "all");
×
UNCOV
2287
    last_sid = 0;
×
UNCOV
2288
    SCJbOpenArray(ctx.js, "rules");
×
UNCOV
2289
    for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) {
×
UNCOV
2290
        if (last_sid == s->id)
×
UNCOV
2291
            continue;
×
UNCOV
2292
        last_sid = s->id;
×
UNCOV
2293
        SCJbAppendString(ctx.js, s->sig_str);
×
UNCOV
2294
    }
×
UNCOV
2295
    SCJbClose(ctx.js); // rules
×
UNCOV
2296
    SCJbClose(ctx.js); // all
×
2297

UNCOV
2298
    SCJbClose(ctx.js); // lists
×
2299

UNCOV
2300
    SCJbClose(ctx.js); // top level object
×
2301

UNCOV
2302
    const char *filename = "firewall.json";
×
UNCOV
2303
    const char *log_dir = SCConfigGetLogDirectory();
×
UNCOV
2304
    char json_path[PATH_MAX] = "";
×
UNCOV
2305
    snprintf(json_path, sizeof(json_path), "%s/%s", log_dir, filename);
×
2306

UNCOV
2307
    SCMutexLock(&g_rules_analyzer_write_m);
×
UNCOV
2308
    FILE *fp = fopen(json_path, "w");
×
UNCOV
2309
    if (fp != NULL) {
×
UNCOV
2310
        fwrite(SCJbPtr(ctx.js), SCJbLen(ctx.js), 1, fp);
×
UNCOV
2311
        fprintf(fp, "\n");
×
UNCOV
2312
        fclose(fp);
×
UNCOV
2313
    }
×
UNCOV
2314
    SCMutexUnlock(&g_rules_analyzer_write_m);
×
UNCOV
2315
    SCJbFree(ctx.js);
×
UNCOV
2316
    return 0;
×
UNCOV
2317
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc