1 /* Copyright (C) 2007-2020 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 "detect.h"
31 #include "detect-parse.h"
32 #include "detect-engine.h"
33 #include "detect-engine-analyzer.h"
34 #include "detect-engine-mpm.h"
35 #include "conf.h"
36 #include "detect-content.h"
37 #include "detect-flow.h"
38 #include "detect-tcp-flags.h"
39 #include "feature.h"
40 #include "util-print.h"
41
42 static int rule_warnings_only = 0;
43 static FILE *rule_engine_analysis_FD = NULL;
44 static FILE *fp_engine_analysis_FD = NULL;
45 static pcre *percent_re = NULL;
46 static pcre_extra *percent_re_study = NULL;
47 static char log_path[PATH_MAX];
48
49 typedef struct FpPatternStats_ {
50 uint16_t min;
51 uint16_t max;
52 uint32_t cnt;
53 uint64_t tot;
54 } FpPatternStats;
55
56 /* Details for each buffer being tracked */
57 typedef struct DetectEngineAnalyzerItems {
58 int16_t item_id;
59 bool item_seen;
60 bool export_item_seen;
61 bool check_encoding_match;
62 const char *item_name;
63 const char *display_name;
64 } DetectEngineAnalyzerItems;
65
66 /* Track which items require the item_seen value to be exposed */
67 struct ExposedItemSeen {
68 const char *bufname;
69 bool *item_seen_ptr;
70 };
71
72 DetectEngineAnalyzerItems analyzer_items[] = {
73 /* request keywords */
74 { 0, false, false, true, "http_uri", "http uri" },
75 { 0, false, false, false, "http_raw_uri", "http raw uri" },
76 { 0, false, true, false, "http_method", "http method" },
77 { 0, false, false, false, "http_request_line", "http request line" },
78 { 0, false, false, false, "http_client_body", "http client body" },
79 { 0, false, false, true, "http_header", "http header" },
80 { 0, false, false, false, "http_raw_header", "http raw header" },
81 { 0, false, false, true, "http_cookie", "http cookie" },
82 { 0, false, false, false, "http_user_agent", "http user agent" },
83 { 0, false, false, false, "http_host", "http host" },
84 { 0, false, false, false, "http_raw_host", "http raw host" },
85 { 0, false, false, false, "http_accept_enc", "http accept enc" },
86 { 0, false, false, false, "http_referer", "http referer" },
87 { 0, false, false, false, "http_content_type", "http content type" },
88 { 0, false, false, false, "http_header_names", "http header names" },
89
90 /* response keywords not listed above */
91 { 0, false, false, false, "http_stat_msg", "http stat msg" },
92 { 0, false, false, false, "http_stat_code", "http stat code" },
93 { 0, false, true, false, "file_data", "http server body"},
94
95 /* missing request keywords */
96 { 0, false, false, false, "http_request_line", "http request line" },
97 { 0, false, false, false, "http_accept", "http accept" },
98 { 0, false, false, false, "http_accept_lang", "http accept lang" },
99 { 0, false, false, false, "http_connection", "http connection" },
100 { 0, false, false, false, "http_content_len", "http content len" },
101 { 0, false, false, false, "http_protocol", "http protocol" },
102 { 0, false, false, false, "http_start", "http start" },
103
104 /* missing response keywords; some of the missing are listed above*/
105 { 0, false, false, false, "http_response_line", "http response line" },
106 { 0, false, false, false, "http.server", "http server" },
107 { 0, false, false, false, "http.location", "http location" },
108 };
109
110 /*
111 * This array contains the map between the `analyzer_items` array listed above and
112 * the item ids returned by DetectBufferTypeGetByName. Iterating signature's sigmatch
113 * array provides list_ids. The map converts those ids into elements of the
114 * analyzer items array.
115 *
116 * Ultimately, the g_buffer_type_hash is searched for each buffer name. The size of that
117 * hashlist is 256, so that's the value we use here.
118 */
119 int16_t analyzer_item_map[256];
120
121 /*
122 * Certain values must be directly accessible. This array contains items that are directly
123 * accessed when checking if they've been seen or not.
124 */
125 struct ExposedItemSeen exposed_item_seen_list[] = {
126 { .bufname = "http_method"},
127 { .bufname = "file_data"}
128 };
129
130 static FpPatternStats fp_pattern_stats[DETECT_SM_LIST_MAX];
131
FpPatternStatsAdd(int list,uint16_t patlen)132 static void FpPatternStatsAdd(int list, uint16_t patlen)
133 {
134 if (list < 0 || list >= DETECT_SM_LIST_MAX)
135 return;
136
137 FpPatternStats *f = &fp_pattern_stats[list];
138
139 if (f->min == 0)
140 f->min = patlen;
141 else if (patlen < f->min)
142 f->min = patlen;
143
144 if (patlen > f->max)
145 f->max = patlen;
146
147 f->cnt++;
148 f->tot += patlen;
149 }
150
EngineAnalysisFP(const DetectEngineCtx * de_ctx,const Signature * s,char * line)151 void EngineAnalysisFP(const DetectEngineCtx *de_ctx, const Signature *s, char *line)
152 {
153 int fast_pattern_set = 0;
154 int fast_pattern_only_set = 0;
155 int fast_pattern_chop_set = 0;
156 DetectContentData *fp_cd = NULL;
157 SigMatch *mpm_sm = s->init_data->mpm_sm;
158
159 if (mpm_sm != NULL) {
160 fp_cd = (DetectContentData *)mpm_sm->ctx;
161 if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN) {
162 fast_pattern_set = 1;
163 if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN_ONLY) {
164 fast_pattern_only_set = 1;
165 } else if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN_CHOP) {
166 fast_pattern_chop_set = 1;
167 }
168 }
169 }
170
171 fprintf(fp_engine_analysis_FD, "== Sid: %u ==\n", s->id);
172 fprintf(fp_engine_analysis_FD, "%s\n", line);
173
174 fprintf(fp_engine_analysis_FD, " Fast Pattern analysis:\n");
175 if (s->init_data->prefilter_sm != NULL) {
176 fprintf(fp_engine_analysis_FD, " Prefilter on: %s\n",
177 sigmatch_table[s->init_data->prefilter_sm->type].name);
178 fprintf(fp_engine_analysis_FD, "\n");
179 return;
180 }
181
182 if (fp_cd == NULL) {
183 fprintf(fp_engine_analysis_FD, " No content present\n");
184 fprintf(fp_engine_analysis_FD, "\n");
185 return;
186 }
187
188 fprintf(fp_engine_analysis_FD, " Fast pattern matcher: ");
189 int list_type = SigMatchListSMBelongsTo(s, mpm_sm);
190 if (list_type == DETECT_SM_LIST_PMATCH)
191 fprintf(fp_engine_analysis_FD, "content\n");
192 else {
193 const char *desc = DetectBufferTypeGetDescriptionById(de_ctx, list_type);
194 const char *name = DetectBufferTypeGetNameById(de_ctx, list_type);
195 if (desc && name) {
196 fprintf(fp_engine_analysis_FD, "%s (%s)\n", desc, name);
197 }
198 }
199
200 int flags_set = 0;
201 fprintf(fp_engine_analysis_FD, " Flags:");
202 if (fp_cd->flags & DETECT_CONTENT_OFFSET) {
203 fprintf(fp_engine_analysis_FD, " Offset");
204 flags_set = 1;
205 } if (fp_cd->flags & DETECT_CONTENT_DEPTH) {
206 fprintf(fp_engine_analysis_FD, " Depth");
207 flags_set = 1;
208 }
209 if (fp_cd->flags & DETECT_CONTENT_WITHIN) {
210 fprintf(fp_engine_analysis_FD, " Within");
211 flags_set = 1;
212 }
213 if (fp_cd->flags & DETECT_CONTENT_DISTANCE) {
214 fprintf(fp_engine_analysis_FD, " Distance");
215 flags_set = 1;
216 }
217 if (fp_cd->flags & DETECT_CONTENT_NOCASE) {
218 fprintf(fp_engine_analysis_FD, " Nocase");
219 flags_set = 1;
220 }
221 if (fp_cd->flags & DETECT_CONTENT_NEGATED) {
222 fprintf(fp_engine_analysis_FD, " Negated");
223 flags_set = 1;
224 }
225 if (flags_set == 0)
226 fprintf(fp_engine_analysis_FD, " None");
227 fprintf(fp_engine_analysis_FD, "\n");
228
229 fprintf(fp_engine_analysis_FD, " Fast pattern set: %s\n", fast_pattern_set ? "yes" : "no");
230 fprintf(fp_engine_analysis_FD, " Fast pattern only set: %s\n",
231 fast_pattern_only_set ? "yes" : "no");
232 fprintf(fp_engine_analysis_FD, " Fast pattern chop set: %s\n",
233 fast_pattern_chop_set ? "yes" : "no");
234 if (fast_pattern_chop_set) {
235 fprintf(fp_engine_analysis_FD, " Fast pattern offset, length: %u, %u\n",
236 fp_cd->fp_chop_offset, fp_cd->fp_chop_len);
237 }
238
239 uint16_t patlen = fp_cd->content_len;
240 uint8_t *pat = SCMalloc(fp_cd->content_len + 1);
241 if (unlikely(pat == NULL)) {
242 FatalError(SC_ERR_FATAL, "Error allocating memory");
243 }
244 memcpy(pat, fp_cd->content, fp_cd->content_len);
245 pat[fp_cd->content_len] = '\0';
246 fprintf(fp_engine_analysis_FD, " Original content: ");
247 PrintRawUriFp(fp_engine_analysis_FD, pat, patlen);
248 fprintf(fp_engine_analysis_FD, "\n");
249
250 if (fast_pattern_chop_set) {
251 SCFree(pat);
252 patlen = fp_cd->fp_chop_len;
253 pat = SCMalloc(fp_cd->fp_chop_len + 1);
254 if (unlikely(pat == NULL)) {
255 exit(EXIT_FAILURE);
256 }
257 memcpy(pat, fp_cd->content + fp_cd->fp_chop_offset, fp_cd->fp_chop_len);
258 pat[fp_cd->fp_chop_len] = '\0';
259 fprintf(fp_engine_analysis_FD, " Final content: ");
260 PrintRawUriFp(fp_engine_analysis_FD, pat, patlen);
261 fprintf(fp_engine_analysis_FD, "\n");
262
263 FpPatternStatsAdd(list_type, patlen);
264 } else {
265 fprintf(fp_engine_analysis_FD, " Final content: ");
266 PrintRawUriFp(fp_engine_analysis_FD, pat, patlen);
267 fprintf(fp_engine_analysis_FD, "\n");
268
269 FpPatternStatsAdd(list_type, patlen);
270 }
271 SCFree(pat);
272
273 fprintf(fp_engine_analysis_FD, "\n");
274 return;
275 }
276
277 /**
278 * \brief Sets up the fast pattern analyzer according to the config.
279 *
280 * \retval 1 If rule analyzer successfully enabled.
281 * \retval 0 If not enabled.
282 */
SetupFPAnalyzer(void)283 int SetupFPAnalyzer(void)
284 {
285 int fp_engine_analysis_set = 0;
286
287 if ((ConfGetBool("engine-analysis.rules-fast-pattern",
288 &fp_engine_analysis_set)) == 0) {
289 return 0;
290 }
291
292 if (fp_engine_analysis_set == 0)
293 return 0;
294
295 const char *log_dir;
296 log_dir = ConfigGetLogDirectory();
297 snprintf(log_path, sizeof(log_path), "%s/%s", log_dir,
298 "rules_fast_pattern.txt");
299
300 fp_engine_analysis_FD = fopen(log_path, "w");
301 if (fp_engine_analysis_FD == NULL) {
302 SCLogError(SC_ERR_FOPEN, "failed to open %s: %s", log_path,
303 strerror(errno));
304 return 0;
305 }
306
307 SCLogInfo("Engine-Analysis for fast_pattern printed to file - %s",
308 log_path);
309
310 struct timeval tval;
311 struct tm *tms;
312 gettimeofday(&tval, NULL);
313 struct tm local_tm;
314 tms = SCLocalTime(tval.tv_sec, &local_tm);
315 fprintf(fp_engine_analysis_FD, "----------------------------------------------"
316 "---------------------\n");
317 fprintf(fp_engine_analysis_FD, "Date: %" PRId32 "/%" PRId32 "/%04d -- "
318 "%02d:%02d:%02d\n",
319 tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour,
320 tms->tm_min, tms->tm_sec);
321 fprintf(fp_engine_analysis_FD, "----------------------------------------------"
322 "---------------------\n");
323
324 memset(&fp_pattern_stats, 0, sizeof(fp_pattern_stats));
325 return 1;
326 }
327
328 /**
329 * \brief Sets up the rule analyzer according to the config
330 * \retval 1 if rule analyzer successfully enabled
331 * \retval 0 if not enabled
332 */
SetupRuleAnalyzer(void)333 int SetupRuleAnalyzer(void)
334 {
335 ConfNode *conf = ConfGetNode("engine-analysis");
336 int enabled = 0;
337 if (conf != NULL) {
338 const char *value = ConfNodeLookupChildValue(conf, "rules");
339 if (value && ConfValIsTrue(value)) {
340 enabled = 1;
341 } else if (value && strcasecmp(value, "warnings-only") == 0) {
342 enabled = 1;
343 rule_warnings_only = 1;
344 }
345 if (enabled) {
346 const char *log_dir;
347 log_dir = ConfigGetLogDirectory();
348 snprintf(log_path, sizeof(log_path), "%s/%s", log_dir, "rules_analysis.txt");
349 rule_engine_analysis_FD = fopen(log_path, "w");
350 if (rule_engine_analysis_FD == NULL) {
351 SCLogError(SC_ERR_FOPEN, "failed to open %s: %s", log_path, strerror(errno));
352 return 0;
353 }
354
355 SCLogInfo("Engine-Analysis for rules printed to file - %s",
356 log_path);
357
358 struct timeval tval;
359 struct tm *tms;
360 gettimeofday(&tval, NULL);
361 struct tm local_tm;
362 tms = SCLocalTime(tval.tv_sec, &local_tm);
363 fprintf(rule_engine_analysis_FD, "----------------------------------------------"
364 "---------------------\n");
365 fprintf(rule_engine_analysis_FD, "Date: %" PRId32 "/%" PRId32 "/%04d -- "
366 "%02d:%02d:%02d\n",
367 tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour,
368 tms->tm_min, tms->tm_sec);
369 fprintf(rule_engine_analysis_FD, "----------------------------------------------"
370 "---------------------\n");
371
372 /*compile regex's for rule analysis*/
373 if (PerCentEncodingSetup()== 0) {
374 fprintf(rule_engine_analysis_FD, "Error compiling regex; can't check for percent encoding in normalized http content.\n");
375 }
376 }
377 }
378 else {
379 SCLogInfo("Conf parameter \"engine-analysis.rules\" not found. "
380 "Defaulting to not printing the rules analysis report.");
381 }
382 if (!enabled) {
383 SCLogInfo("Engine-Analysis for rules disabled in conf file.");
384 return 0;
385 }
386 return 1;
387 }
388
CleanupFPAnalyzer(void)389 void CleanupFPAnalyzer(void)
390 {
391 fprintf(fp_engine_analysis_FD, "============\n"
392 "Summary:\n============\n");
393 int i;
394 for (i = 0; i < DETECT_SM_LIST_MAX; i++) {
395 FpPatternStats *f = &fp_pattern_stats[i];
396 if (f->cnt == 0)
397 continue;
398
399 fprintf(fp_engine_analysis_FD,
400 "%s, smallest pattern %u byte(s), longest pattern %u byte(s), number of patterns %u, avg pattern len %.2f byte(s)\n",
401 DetectSigmatchListEnumToString(i), f->min, f->max, f->cnt, (float)((double)f->tot/(float)f->cnt));
402 }
403
404 if (fp_engine_analysis_FD != NULL) {
405 fclose(fp_engine_analysis_FD);
406 fp_engine_analysis_FD = NULL;
407 }
408
409 return;
410 }
411
412
CleanupRuleAnalyzer(void)413 void CleanupRuleAnalyzer(void)
414 {
415 if (rule_engine_analysis_FD != NULL) {
416 SCLogInfo("Engine-Analysis for rules printed to file - %s", log_path);
417 fclose(rule_engine_analysis_FD);
418 rule_engine_analysis_FD = NULL;
419 }
420 }
421
422 /**
423 * \brief Compiles regex for rule analysis
424 * \retval 1 if successful
425 * \retval 0 if on error
426 */
PerCentEncodingSetup()427 int PerCentEncodingSetup ()
428 {
429 #define DETECT_PERCENT_ENCODING_REGEX "%[0-9|a-f|A-F]{2}"
430 const char *eb = NULL;
431 int eo = 0;
432 int opts = 0; //PCRE_NEWLINE_ANY??
433
434 percent_re = pcre_compile(DETECT_PERCENT_ENCODING_REGEX, opts, &eb, &eo, NULL);
435 if (percent_re == NULL) {
436 SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s",
437 DETECT_PERCENT_ENCODING_REGEX, eo, eb);
438 return 0;
439 }
440
441 percent_re_study = pcre_study(percent_re, 0, &eb);
442 if (eb != NULL) {
443 SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb);
444 return 0;
445 }
446 return 1;
447 }
448
449 /**
450 * \brief Checks for % encoding in content.
451 * \param Pointer to content
452 * \retval number of matches if content has % encoding
453 * \retval 0 if it doesn't have % encoding
454 * \retval -1 on error
455 */
PerCentEncodingMatch(uint8_t * content,uint8_t content_len)456 int PerCentEncodingMatch (uint8_t *content, uint8_t content_len)
457 {
458 #define MAX_ENCODED_CHARS 240
459 int ret = 0;
460 int ov[MAX_ENCODED_CHARS];
461
462 ret = pcre_exec(percent_re, percent_re_study, (char *)content, content_len, 0, 0, ov, MAX_ENCODED_CHARS);
463 if (ret == -1) {
464 return 0;
465 }
466 else if (ret < -1) {
467 SCLogError(SC_ERR_PCRE_MATCH, "Error parsing content - %s; error code is %d", content, ret);
468 return -1;
469 }
470 return ret;
471 }
472
EngineAnalysisRulesPrintFP(const DetectEngineCtx * de_ctx,const Signature * s)473 static void EngineAnalysisRulesPrintFP(const DetectEngineCtx *de_ctx, const Signature *s)
474 {
475 DetectContentData *fp_cd = NULL;
476 SigMatch *mpm_sm = s->init_data->mpm_sm;
477
478 if (mpm_sm != NULL) {
479 fp_cd = (DetectContentData *)mpm_sm->ctx;
480 }
481
482 if (fp_cd == NULL) {
483 return;
484 }
485
486 uint16_t patlen = fp_cd->content_len;
487 uint8_t *pat = SCMalloc(fp_cd->content_len + 1);
488 if (unlikely(pat == NULL)) {
489 FatalError(SC_ERR_FATAL, "Error allocating memory");
490 }
491 memcpy(pat, fp_cd->content, fp_cd->content_len);
492 pat[fp_cd->content_len] = '\0';
493
494 if (fp_cd->flags & DETECT_CONTENT_FAST_PATTERN_CHOP) {
495 SCFree(pat);
496 patlen = fp_cd->fp_chop_len;
497 pat = SCMalloc(fp_cd->fp_chop_len + 1);
498 if (unlikely(pat == NULL)) {
499 exit(EXIT_FAILURE);
500 }
501 memcpy(pat, fp_cd->content + fp_cd->fp_chop_offset, fp_cd->fp_chop_len);
502 pat[fp_cd->fp_chop_len] = '\0';
503 fprintf(rule_engine_analysis_FD, " Fast Pattern \"");
504 PrintRawUriFp(rule_engine_analysis_FD, pat, patlen);
505 } else {
506 fprintf(rule_engine_analysis_FD, " Fast Pattern \"");
507 PrintRawUriFp(rule_engine_analysis_FD, pat, patlen);
508 }
509 SCFree(pat);
510
511 fprintf(rule_engine_analysis_FD, "\" on \"");
512
513 int list_type = SigMatchListSMBelongsTo(s, mpm_sm);
514 if (list_type == DETECT_SM_LIST_PMATCH) {
515 int payload = 0;
516 int stream = 0;
517 if (SignatureHasPacketContent(s))
518 payload = 1;
519 if (SignatureHasStreamContent(s))
520 stream = 1;
521 fprintf(rule_engine_analysis_FD, "%s",
522 payload ? (stream ? "payload and reassembled stream" : "payload") : "reassembled stream");
523 }
524 else {
525 const char *desc = DetectBufferTypeGetDescriptionById(de_ctx, list_type);
526 const char *name = DetectBufferTypeGetNameById(de_ctx, list_type);
527 if (desc && name) {
528 fprintf(rule_engine_analysis_FD, "%s (%s)", desc, name);
529 } else if (desc || name) {
530 fprintf(rule_engine_analysis_FD, "%s", desc ? desc : name);
531 }
532
533 }
534
535 fprintf(rule_engine_analysis_FD, "\" ");
536 if (de_ctx->buffer_type_map[list_type] && de_ctx->buffer_type_map[list_type]->transforms.cnt) {
537 fprintf(rule_engine_analysis_FD, "(with %d transform(s)) ",
538 de_ctx->buffer_type_map[list_type]->transforms.cnt);
539 }
540 fprintf(rule_engine_analysis_FD, "buffer.\n");
541
542 return;
543 }
544
545
EngineAnalysisRulesFailure(char * line,char * file,int lineno)546 void EngineAnalysisRulesFailure(char *line, char *file, int lineno)
547 {
548 fprintf(rule_engine_analysis_FD, "== Sid: UNKNOWN ==\n");
549 fprintf(rule_engine_analysis_FD, "%s\n", line);
550 fprintf(rule_engine_analysis_FD, " FAILURE: invalid rule.\n");
551 fprintf(rule_engine_analysis_FD, " File: %s.\n", file);
552 fprintf(rule_engine_analysis_FD, " Line: %d.\n", lineno);
553 fprintf(rule_engine_analysis_FD, "\n");
554 }
555
556 typedef struct RuleAnalyzer {
557 JsonBuilder *js; /* document root */
558
559 JsonBuilder *js_warnings;
560 JsonBuilder *js_notes;
561 } RuleAnalyzer;
562
AnalyzerNote(RuleAnalyzer * ctx,char * fmt,...)563 static void ATTR_FMT_PRINTF(2, 3) AnalyzerNote(RuleAnalyzer *ctx, char *fmt, ...)
564 {
565 va_list ap;
566 char str[1024];
567
568 va_start(ap, fmt);
569 vsnprintf(str, sizeof(str), fmt, ap);
570 va_end(ap);
571
572 if (!ctx->js_notes)
573 ctx->js_notes = jb_new_array();
574 if (ctx->js_notes)
575 jb_append_string(ctx->js_notes, str);
576 }
577
AnalyzerWarning(RuleAnalyzer * ctx,char * fmt,...)578 static void ATTR_FMT_PRINTF(2, 3) AnalyzerWarning(RuleAnalyzer *ctx, char *fmt, ...)
579 {
580 va_list ap;
581 char str[1024];
582
583 va_start(ap, fmt);
584 vsnprintf(str, sizeof(str), fmt, ap);
585 va_end(ap);
586
587 if (!ctx->js_warnings)
588 ctx->js_warnings = jb_new_array();
589 if (ctx->js_warnings)
590 jb_append_string(ctx->js_warnings, str);
591 }
592
593 #define CHECK(pat) if (strlen((pat)) <= len && memcmp((pat), buf, MIN(len, strlen((pat)))) == 0) return true;
594
LooksLikeHTTPMethod(const uint8_t * buf,uint16_t len)595 static bool LooksLikeHTTPMethod(const uint8_t *buf, uint16_t len)
596 {
597 CHECK("GET /");
598 CHECK("POST /");
599 CHECK("HEAD /");
600 CHECK("PUT /");
601 return false;
602 }
603
LooksLikeHTTPUA(const uint8_t * buf,uint16_t len)604 static bool LooksLikeHTTPUA(const uint8_t *buf, uint16_t len)
605 {
606 CHECK("User-Agent: ");
607 CHECK("\nUser-Agent: ");
608 return false;
609 }
610
DumpMatches(RuleAnalyzer * ctx,JsonBuilder * js,const SigMatchData * smd)611 static void DumpMatches(RuleAnalyzer *ctx, JsonBuilder *js, const SigMatchData *smd)
612 {
613 if (smd == NULL)
614 return;
615
616 jb_open_array(js, "matches");
617 do {
618 jb_start_object(js);
619 const char *mname = sigmatch_table[smd->type].name;
620 jb_set_string(js, "name", mname);
621
622 switch (smd->type) {
623 case DETECT_CONTENT: {
624 const DetectContentData *cd = (const DetectContentData *)smd->ctx;
625
626 jb_open_object(js, "content");
627 jb_set_string_from_bytes(js, "pattern", cd->content, cd->content_len);
628 jb_set_bool(js, "nocase", cd->flags & DETECT_CONTENT_NOCASE);
629 jb_set_bool(js, "negated", cd->flags & DETECT_CONTENT_NEGATED);
630 jb_set_bool(js, "starts_with", cd->flags & DETECT_CONTENT_STARTS_WITH);
631 jb_set_bool(js, "ends_with", cd->flags & DETECT_CONTENT_ENDS_WITH);
632 jb_set_bool(js, "is_mpm", cd->flags & DETECT_CONTENT_MPM);
633 if (cd->flags & DETECT_CONTENT_OFFSET) {
634 jb_set_uint(js, "offset", cd->offset);
635 }
636 if (cd->flags & DETECT_CONTENT_DEPTH) {
637 jb_set_uint(js, "depth", cd->depth);
638 }
639 if (cd->flags & DETECT_CONTENT_DISTANCE) {
640 jb_set_uint(js, "distance", cd->distance);
641 }
642 if (cd->flags & DETECT_CONTENT_WITHIN) {
643 jb_set_uint(js, "within", cd->within);
644 }
645 jb_set_bool(js, "fast_pattern", cd->flags & DETECT_CONTENT_FAST_PATTERN);
646 if (cd->flags & DETECT_CONTENT_FAST_PATTERN_ONLY) {
647 AnalyzerNote(ctx, (char *)"'fast_pattern:only' option is silently ignored and "
648 "is interpreted as regular 'fast_pattern'");
649 }
650 if (LooksLikeHTTPMethod(cd->content, cd->content_len)) {
651 AnalyzerWarning(ctx,
652 (char *)"pattern looks like it inspects HTTP, use http.request_line or "
653 "http.method and http.uri instead for improved performance");
654 }
655 if (LooksLikeHTTPUA(cd->content, cd->content_len)) {
656 AnalyzerWarning(ctx,
657 (char *)"pattern looks like it inspects HTTP, use http.user_agent "
658 "or http.header for improved performance");
659 }
660 jb_close(js);
661 break;
662 }
663 }
664 jb_close(js);
665
666 if (smd->is_last)
667 break;
668 smd++;
669 } while (1);
670 jb_close(js);
671 }
672
673 SCMutex g_rules_analyzer_write_m = SCMUTEX_INITIALIZER;
EngineAnalysisRules2(const DetectEngineCtx * de_ctx,const Signature * s)674 void EngineAnalysisRules2(const DetectEngineCtx *de_ctx, const Signature *s)
675 {
676 SCEnter();
677
678 RuleAnalyzer ctx = { NULL, NULL, NULL };
679
680 ctx.js = jb_new_object();
681 if (ctx.js == NULL)
682 SCReturn;
683
684 jb_set_string(ctx.js, "raw", s->sig_str);
685 jb_set_uint(ctx.js, "id", s->id);
686 jb_set_uint(ctx.js, "gid", s->gid);
687 jb_set_uint(ctx.js, "rev", s->rev);
688 jb_set_string(ctx.js, "msg", s->msg);
689
690 const char *alproto = AppProtoToString(s->alproto);
691 jb_set_string(ctx.js, "app_proto", alproto);
692
693 jb_open_array(ctx.js, "requirements");
694 if (s->mask & SIG_MASK_REQUIRE_PAYLOAD) {
695 jb_append_string(ctx.js, "payload");
696 }
697 if (s->mask & SIG_MASK_REQUIRE_NO_PAYLOAD) {
698 jb_append_string(ctx.js, "no_payload");
699 }
700 if (s->mask & SIG_MASK_REQUIRE_FLOW) {
701 jb_append_string(ctx.js, "flow");
702 }
703 if (s->mask & SIG_MASK_REQUIRE_FLAGS_INITDEINIT) {
704 jb_append_string(ctx.js, "tcp_flags_init_deinit");
705 }
706 if (s->mask & SIG_MASK_REQUIRE_FLAGS_UNUSUAL) {
707 jb_append_string(ctx.js, "tcp_flags_unusual");
708 }
709 if (s->mask & SIG_MASK_REQUIRE_DCERPC) {
710 jb_append_string(ctx.js, "dcerpc");
711 }
712 if (s->mask & SIG_MASK_REQUIRE_ENGINE_EVENT) {
713 jb_append_string(ctx.js, "engine_event");
714 }
715 jb_close(ctx.js);
716
717 jb_open_array(ctx.js, "flags");
718 if (s->flags & SIG_FLAG_SRC_ANY) {
719 jb_append_string(ctx.js, "src_any");
720 }
721 if (s->flags & SIG_FLAG_DST_ANY) {
722 jb_append_string(ctx.js, "dst_any");
723 }
724 if (s->flags & SIG_FLAG_SP_ANY) {
725 jb_append_string(ctx.js, "sp_any");
726 }
727 if (s->flags & SIG_FLAG_DP_ANY) {
728 jb_append_string(ctx.js, "dp_any");
729 }
730 if (s->flags & SIG_FLAG_NOALERT) {
731 jb_append_string(ctx.js, "noalert");
732 }
733 if (s->flags & SIG_FLAG_DSIZE) {
734 jb_append_string(ctx.js, "dsize");
735 }
736 if (s->flags & SIG_FLAG_APPLAYER) {
737 jb_append_string(ctx.js, "applayer");
738 }
739 if (s->flags & SIG_FLAG_IPONLY) {
740 jb_append_string(ctx.js, "ip_only");
741 }
742 if (s->flags & SIG_FLAG_REQUIRE_PACKET) {
743 jb_append_string(ctx.js, "need_packet");
744 }
745 if (s->flags & SIG_FLAG_REQUIRE_STREAM) {
746 jb_append_string(ctx.js, "need_stream");
747 }
748 if (s->flags & SIG_FLAG_MPM_NEG) {
749 jb_append_string(ctx.js, "negated_mpm");
750 }
751 if (s->flags & SIG_FLAG_FLUSH) {
752 jb_append_string(ctx.js, "flush");
753 }
754 if (s->flags & SIG_FLAG_REQUIRE_FLOWVAR) {
755 jb_append_string(ctx.js, "need_flowvar");
756 }
757 if (s->flags & SIG_FLAG_FILESTORE) {
758 jb_append_string(ctx.js, "filestore");
759 }
760 if (s->flags & SIG_FLAG_TOSERVER) {
761 jb_append_string(ctx.js, "toserver");
762 }
763 if (s->flags & SIG_FLAG_TOCLIENT) {
764 jb_append_string(ctx.js, "toclient");
765 }
766 if (s->flags & SIG_FLAG_TLSSTORE) {
767 jb_append_string(ctx.js, "tlsstore");
768 }
769 if (s->flags & SIG_FLAG_BYPASS) {
770 jb_append_string(ctx.js, "bypass");
771 }
772 if (s->flags & SIG_FLAG_PREFILTER) {
773 jb_append_string(ctx.js, "prefilter");
774 }
775 if (s->flags & SIG_FLAG_PDONLY) {
776 jb_append_string(ctx.js, "proto_detect_only");
777 }
778 if (s->flags & SIG_FLAG_SRC_IS_TARGET) {
779 jb_append_string(ctx.js, "src_is_target");
780 }
781 if (s->flags & SIG_FLAG_DEST_IS_TARGET) {
782 jb_append_string(ctx.js, "dst_is_target");
783 }
784 jb_close(ctx.js);
785
786 jb_open_array(ctx.js, "pkt_engines");
787 const DetectEnginePktInspectionEngine *pkt = s->pkt_inspect;
788 for ( ; pkt != NULL; pkt = pkt->next) {
789 const char *name = DetectBufferTypeGetNameById(de_ctx, pkt->sm_list);
790 if (name == NULL) {
791 switch (pkt->sm_list) {
792 case DETECT_SM_LIST_PMATCH:
793 name = "payload";
794 break;
795 case DETECT_SM_LIST_MATCH:
796 name = "packet";
797 break;
798 default:
799 name = "unknown";
800 break;
801 }
802 }
803 jb_start_object(ctx.js);
804 jb_set_string(ctx.js, "name", name);
805 jb_set_bool(ctx.js, "is_mpm", pkt->mpm);
806 DumpMatches(&ctx, ctx.js, pkt->smd);
807 jb_close(ctx.js);
808 }
809 jb_close(ctx.js);
810
811 if (s->init_data->init_flags & SIG_FLAG_INIT_STATE_MATCH) {
812 bool has_stream = false;
813 bool has_client_body_mpm = false;
814 bool has_file_data_mpm = false;
815
816 jb_open_array(ctx.js, "engines");
817 const DetectEngineAppInspectionEngine *app = s->app_inspect;
818 for ( ; app != NULL; app = app->next) {
819 const char *name = DetectBufferTypeGetNameById(de_ctx, app->sm_list);
820 if (name == NULL) {
821 switch (app->sm_list) {
822 case DETECT_SM_LIST_PMATCH:
823 name = "stream";
824 break;
825 default:
826 name = "unknown";
827 break;
828 }
829 }
830
831 if (app->sm_list == DETECT_SM_LIST_PMATCH && !app->mpm) {
832 has_stream = true;
833 } else if (app->mpm && strcmp(name, "http_client_body") == 0) {
834 has_client_body_mpm = true;
835 } else if (app->mpm && strcmp(name, "file_data") == 0) {
836 has_file_data_mpm = true;
837 }
838
839 jb_start_object(ctx.js);
840 jb_set_string(ctx.js, "name", name);
841 const char *direction = app->dir == 0 ? "toserver" : "toclient";
842 jb_set_string(ctx.js, "direction", direction);
843 jb_set_bool(ctx.js, "is_mpm", app->mpm);
844 jb_set_string(ctx.js, "app_proto", AppProtoToString(app->alproto));
845 jb_set_uint(ctx.js, "progress", app->progress);
846 DumpMatches(&ctx, ctx.js, app->smd);
847 jb_close(ctx.js);
848 }
849 jb_close(ctx.js);
850
851 if (has_stream && has_client_body_mpm)
852 AnalyzerNote(&ctx, (char *)"mpm in http_client_body combined with stream match leads to stream buffering");
853 if (has_stream && has_file_data_mpm)
854 AnalyzerNote(&ctx, (char *)"mpm in file_data combined with stream match leads to stream buffering");
855 }
856
857 jb_open_object(ctx.js, "lists");
858 for (int i = 0; i < DETECT_SM_LIST_MAX; i++) {
859 if (s->sm_arrays[i] != NULL) {
860 jb_open_object(ctx.js, DetectSigmatchListEnumToString(i));
861 DumpMatches(&ctx, ctx.js, s->sm_arrays[i]);
862 jb_close(ctx.js);
863 }
864 }
865 jb_close(ctx.js);
866
867 if (ctx.js_warnings) {
868 jb_close(ctx.js_warnings);
869 jb_set_object(ctx.js, "warnings", ctx.js_warnings);
870 jb_free(ctx.js_warnings);
871 ctx.js_warnings = NULL;
872 }
873 if (ctx.js_notes) {
874 jb_close(ctx.js_notes);
875 jb_set_object(ctx.js, "notes", ctx.js_notes);
876 jb_free(ctx.js_notes);
877 ctx.js_notes = NULL;
878 }
879 jb_close(ctx.js);
880
881 const char *filename = "rules.json";
882 const char *log_dir = ConfigGetLogDirectory();
883 char json_path[PATH_MAX] = "";
884 snprintf(json_path, sizeof(json_path), "%s/%s", log_dir, filename);
885
886 SCMutexLock(&g_rules_analyzer_write_m);
887 FILE *fp = fopen(json_path, "a");
888 if (fp != NULL) {
889 fwrite(jb_ptr(ctx.js), jb_len(ctx.js), 1, fp);
890 fprintf(fp, "\n");
891 fclose(fp);
892 }
893 SCMutexUnlock(&g_rules_analyzer_write_m);
894 jb_free(ctx.js);
895 SCReturn;
896 }
897
EngineAnalysisItemsReset(void)898 static void EngineAnalysisItemsReset(void)
899 {
900 for (size_t i = 0; i < ARRAY_SIZE(analyzer_items); i++) {
901 analyzer_items[i].item_seen = false;
902 }
903 }
904
EngineAnalysisItemsInit(void)905 static void EngineAnalysisItemsInit(void)
906 {
907 static bool analyzer_init = false;
908
909 if (analyzer_init) {
910 EngineAnalysisItemsReset();
911 return;
912 }
913
914 memset(analyzer_item_map, -1, sizeof(analyzer_item_map));
915
916 for (size_t i = 0; i < ARRAY_SIZE(analyzer_items); i++) {
917 DetectEngineAnalyzerItems *analyzer_item = &analyzer_items[i];
918
919 analyzer_item->item_id = DetectBufferTypeGetByName(analyzer_item->item_name);
920 if (analyzer_item->item_id == -1) {
921 /* Mismatch between the analyzer_items array and what's supported */
922 FatalError(SC_ERR_INITIALIZATION,
923 "unable to initialize engine-analysis table: detect buffer \"%s\" not recognized.",
924 analyzer_item->item_name);
925 }
926 analyzer_item->item_seen = false;
927
928 if (analyzer_item->export_item_seen) {
929 for (size_t k = 0; k < ARRAY_SIZE(exposed_item_seen_list); k++) {
930 if (0 == strcmp(exposed_item_seen_list[k].bufname, analyzer_item->item_name))
931 exposed_item_seen_list[k].item_seen_ptr = &analyzer_item->item_seen;
932 }
933
934 }
935 analyzer_item_map[analyzer_item->item_id] = (int16_t) i;
936 }
937
938 analyzer_init = true;
939 }
940
941 /**
942 * \brief Prints analysis of loaded rules.
943 *
944 * Warns if potential rule issues are detected. For example,
945 * warns if a rule uses a construct that may perform poorly,
946 * e.g. pcre without content or with http_method content only;
947 * warns if a rule uses a construct that may not be consistent with intent,
948 * e.g. client side ports only, http and content without any http_* modifiers, etc.
949 *
950 * \param s Pointer to the signature.
951 */
EngineAnalysisRules(const DetectEngineCtx * de_ctx,const Signature * s,const char * line)952 void EngineAnalysisRules(const DetectEngineCtx *de_ctx,
953 const Signature *s, const char *line)
954 {
955 uint32_t rule_bidirectional = 0;
956 uint32_t rule_pcre = 0;
957 uint32_t rule_pcre_http = 0;
958 uint32_t rule_content = 0;
959 uint32_t rule_flow = 0;
960 uint32_t rule_flags = 0;
961 uint32_t rule_flow_toserver = 0;
962 uint32_t rule_flow_toclient = 0;
963 uint32_t rule_flow_nostream = 0;
964 uint32_t rule_ipv4_only = 0;
965 uint32_t rule_ipv6_only = 0;
966 uint32_t rule_flowbits = 0;
967 uint32_t rule_flowint = 0;
968 uint32_t rule_content_http = 0;
969 uint32_t rule_content_offset_depth = 0;
970 int32_t list_id = 0;
971 uint32_t rule_warning = 0;
972 uint32_t stream_buf = 0;
973 uint32_t packet_buf = 0;
974 uint32_t file_store = 0;
975 uint32_t warn_pcre_no_content = 0;
976 uint32_t warn_pcre_http_content = 0;
977 uint32_t warn_pcre_http = 0;
978 uint32_t warn_content_http_content = 0;
979 uint32_t warn_content_http = 0;
980 uint32_t warn_tcp_no_flow = 0;
981 uint32_t warn_client_ports = 0;
982 uint32_t warn_direction = 0;
983 uint32_t warn_method_toclient = 0;
984 uint32_t warn_method_serverbody = 0;
985 uint32_t warn_pcre_method = 0;
986 uint32_t warn_encoding_norm_http_buf = 0;
987 uint32_t warn_file_store_not_present = 0;
988 uint32_t warn_offset_depth_pkt_stream = 0;
989 uint32_t warn_offset_depth_alproto = 0;
990 uint32_t warn_non_alproto_fp_for_alproto_sig = 0;
991 uint32_t warn_no_direction = 0;
992 uint32_t warn_both_direction = 0;
993
994 EngineAnalysisItemsInit();
995
996 bool *http_method_item_seen_ptr = exposed_item_seen_list[0].item_seen_ptr;
997 bool *http_server_body_item_seen_ptr = exposed_item_seen_list[1].item_seen_ptr;
998
999 if (s->init_data->init_flags & SIG_FLAG_INIT_BIDIREC) {
1000 rule_bidirectional = 1;
1001 }
1002
1003 if (s->flags & SIG_FLAG_REQUIRE_PACKET) {
1004 packet_buf += 1;
1005 }
1006 if (s->flags & SIG_FLAG_FILESTORE) {
1007 file_store += 1;
1008 }
1009 if (s->flags & SIG_FLAG_REQUIRE_STREAM) {
1010 stream_buf += 1;
1011 }
1012
1013 if (s->proto.flags & DETECT_PROTO_IPV4) {
1014 rule_ipv4_only += 1;
1015 }
1016 if (s->proto.flags & DETECT_PROTO_IPV6) {
1017 rule_ipv6_only += 1;
1018 }
1019
1020 for (list_id = 0; list_id < (int)s->init_data->smlists_array_size; list_id++) {
1021 SigMatch *sm = NULL;
1022 for (sm = s->init_data->smlists[list_id]; sm != NULL; sm = sm->next) {
1023 int16_t item_slot = analyzer_item_map[list_id];
1024 if (sm->type == DETECT_PCRE) {
1025 if (item_slot == -1) {
1026 rule_pcre++;
1027 continue;
1028 }
1029
1030 rule_pcre_http++;
1031 analyzer_items[item_slot].item_seen = true;
1032 } else if (sm->type == DETECT_CONTENT) {
1033 if (item_slot == -1) {
1034 rule_content++;
1035 if (list_id == DETECT_SM_LIST_PMATCH) {
1036 DetectContentData *cd = (DetectContentData *)sm->ctx;
1037 if (cd->flags & (DETECT_CONTENT_OFFSET | DETECT_CONTENT_DEPTH)) {
1038 rule_content_offset_depth++;
1039 }
1040 }
1041 continue;
1042 }
1043
1044 rule_content_http++;
1045 analyzer_items[item_slot].item_seen = true;
1046
1047 if (analyzer_items[item_slot].check_encoding_match) {
1048 DetectContentData *cd = (DetectContentData *)sm->ctx;
1049 if (cd != NULL && PerCentEncodingMatch(cd->content, cd->content_len) > 0) {
1050 warn_encoding_norm_http_buf += 1;
1051 }
1052 }
1053 }
1054 else if (sm->type == DETECT_FLOW) {
1055 rule_flow += 1;
1056 if ((s->flags & SIG_FLAG_TOSERVER) && !(s->flags & SIG_FLAG_TOCLIENT)) {
1057 rule_flow_toserver = 1;
1058 }
1059 else if ((s->flags & SIG_FLAG_TOCLIENT) && !(s->flags & SIG_FLAG_TOSERVER)) {
1060 rule_flow_toclient = 1;
1061 }
1062 DetectFlowData *fd = (DetectFlowData *)sm->ctx;
1063 if (fd != NULL) {
1064 if (fd->flags & DETECT_FLOW_FLAG_NOSTREAM)
1065 rule_flow_nostream = 1;
1066 }
1067 }
1068 else if (sm->type == DETECT_FLOWBITS) {
1069 if (list_id == DETECT_SM_LIST_MATCH) {
1070 rule_flowbits += 1;
1071 }
1072 }
1073 else if (sm->type == DETECT_FLOWINT) {
1074 if (list_id == DETECT_SM_LIST_MATCH) {
1075 rule_flowint += 1;
1076 }
1077 }
1078 else if (sm->type == DETECT_FLAGS) {
1079 DetectFlagsData *fd = (DetectFlagsData *)sm->ctx;
1080 if (fd != NULL) {
1081 rule_flags = 1;
1082 }
1083 }
1084 } /* for (sm = s->sm_lists[list_id]; sm != NULL; sm = sm->next) */
1085
1086 } /* for ( ; list_id < DETECT_SM_LIST_MAX; list_id++) */
1087
1088
1089 if (file_store && !RequiresFeature("output::file-store")) {
1090 rule_warning += 1;
1091 warn_file_store_not_present = 1;
1092 }
1093
1094 if (rule_pcre > 0 && rule_content == 0 && rule_content_http == 0) {
1095 rule_warning += 1;
1096 warn_pcre_no_content = 1;
1097 }
1098
1099 if (rule_content_http > 0 && rule_pcre > 0 && rule_pcre_http == 0) {
1100 rule_warning += 1;
1101 warn_pcre_http_content = 1;
1102 }
1103 else if (s->alproto == ALPROTO_HTTP && rule_pcre > 0 && rule_pcre_http == 0) {
1104 rule_warning += 1;
1105 warn_pcre_http = 1;
1106 }
1107
1108 if (rule_content > 0 && rule_content_http > 0) {
1109 rule_warning += 1;
1110 warn_content_http_content = 1;
1111 }
1112 if (s->alproto == ALPROTO_HTTP && rule_content > 0 && rule_content_http == 0) {
1113 rule_warning += 1;
1114 warn_content_http = 1;
1115 }
1116 if (rule_content == 1) {
1117 //todo: warning if content is weak, separate warning for pcre + weak content
1118 }
1119 if (rule_flow == 0 && rule_flags == 0 && !(s->proto.flags & DETECT_PROTO_ANY) &&
1120 DetectProtoContainsProto(&s->proto, IPPROTO_TCP) &&
1121 (rule_content || rule_content_http || rule_pcre || rule_pcre_http || rule_flowbits ||
1122 rule_flowint)) {
1123 rule_warning += 1;
1124 warn_tcp_no_flow = 1;
1125 }
1126 if (rule_flow && !rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)
1127 && !((s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY))) {
1128 if (((s->flags & SIG_FLAG_TOSERVER) && !(s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY))
1129 || ((s->flags & SIG_FLAG_TOCLIENT) && !(s->flags & SIG_FLAG_DP_ANY) && (s->flags & SIG_FLAG_SP_ANY))) {
1130 rule_warning += 1;
1131 warn_client_ports = 1;
1132 }
1133 }
1134 if (rule_flow && rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)) {
1135 rule_warning += 1;
1136 warn_direction = 1;
1137 }
1138
1139 if (*http_method_item_seen_ptr) {
1140 if (rule_flow && rule_flow_toclient) {
1141 rule_warning += 1;
1142 warn_method_toclient = 1;
1143 }
1144 if (*http_server_body_item_seen_ptr) {
1145 rule_warning += 1;
1146 warn_method_serverbody = 1;
1147 }
1148 if (rule_content == 0 && rule_content_http == 0 && (rule_pcre > 0 || rule_pcre_http > 0)) {
1149 rule_warning += 1;
1150 warn_pcre_method = 1;
1151 }
1152 }
1153 if (rule_content_offset_depth > 0 && stream_buf && packet_buf) {
1154 rule_warning += 1;
1155 warn_offset_depth_pkt_stream = 1;
1156 }
1157 if (rule_content_offset_depth > 0 && !stream_buf && packet_buf && s->alproto != ALPROTO_UNKNOWN) {
1158 rule_warning += 1;
1159 warn_offset_depth_alproto = 1;
1160 }
1161 if (s->init_data->mpm_sm != NULL && s->alproto == ALPROTO_HTTP &&
1162 SigMatchListSMBelongsTo(s, s->init_data->mpm_sm) == DETECT_SM_LIST_PMATCH) {
1163 rule_warning += 1;
1164 warn_non_alproto_fp_for_alproto_sig = 1;
1165 }
1166
1167 if ((s->flags & (SIG_FLAG_TOSERVER|SIG_FLAG_TOCLIENT)) == 0) {
1168 warn_no_direction += 1;
1169 rule_warning += 1;
1170 }
1171
1172 /* No warning about direction for ICMP protos */
1173 if (!(DetectProtoContainsProto(&s->proto, IPPROTO_ICMPV6) && DetectProtoContainsProto(&s->proto, IPPROTO_ICMP))) {
1174 if ((s->flags & (SIG_FLAG_TOSERVER|SIG_FLAG_TOCLIENT)) == (SIG_FLAG_TOSERVER|SIG_FLAG_TOCLIENT)) {
1175 warn_both_direction += 1;
1176 rule_warning += 1;
1177 }
1178 }
1179
1180 if (!rule_warnings_only || (rule_warnings_only && rule_warning > 0)) {
1181 fprintf(rule_engine_analysis_FD, "== Sid: %u ==\n", s->id);
1182 fprintf(rule_engine_analysis_FD, "%s\n", line);
1183
1184 if (s->flags & SIG_FLAG_IPONLY) fprintf(rule_engine_analysis_FD, " Rule is ip only.\n");
1185 if (s->flags & SIG_FLAG_PDONLY) fprintf(rule_engine_analysis_FD, " Rule is PD only.\n");
1186 if (rule_ipv6_only) fprintf(rule_engine_analysis_FD, " Rule is IPv6 only.\n");
1187 if (rule_ipv4_only) fprintf(rule_engine_analysis_FD, " Rule is IPv4 only.\n");
1188 if (packet_buf) fprintf(rule_engine_analysis_FD, " Rule matches on packets.\n");
1189 if (!rule_flow_nostream && stream_buf &&
1190 (rule_flow || rule_flowbits || rule_flowint || rule_content || rule_pcre)) {
1191 fprintf(rule_engine_analysis_FD, " Rule matches on reassembled stream.\n");
1192 }
1193 for(size_t i = 0; i < ARRAY_SIZE(analyzer_items); i++) {
1194 DetectEngineAnalyzerItems *ai = &analyzer_items[i];
1195 if (ai->item_seen) {
1196 fprintf(rule_engine_analysis_FD, " Rule matches on %s buffer.\n", ai->display_name);
1197 }
1198 }
1199 if (s->alproto != ALPROTO_UNKNOWN) {
1200 fprintf(rule_engine_analysis_FD, " App layer protocol is %s.\n", AppProtoToString(s->alproto));
1201 }
1202 if (rule_content || rule_content_http || rule_pcre || rule_pcre_http) {
1203 fprintf(rule_engine_analysis_FD, " Rule contains %d content options, %d http content options, %d pcre options, and %d pcre options with http modifiers.\n", rule_content, rule_content_http, rule_pcre, rule_pcre_http);
1204 }
1205
1206 /* print fast pattern info */
1207 if (s->init_data->prefilter_sm) {
1208 fprintf(rule_engine_analysis_FD, " Prefilter on: %s.\n",
1209 sigmatch_table[s->init_data->prefilter_sm->type].name);
1210 } else {
1211 EngineAnalysisRulesPrintFP(de_ctx, s);
1212 }
1213
1214 /* this is where the warnings start */
1215 if (warn_pcre_no_content /*rule_pcre > 0 && rule_content == 0 && rule_content_http == 0*/) {
1216 fprintf(rule_engine_analysis_FD, " Warning: Rule uses pcre without a content option present.\n"
1217 " -Consider adding a content to improve performance of this rule.\n");
1218 }
1219 if (warn_pcre_http_content /*rule_content_http > 0 && rule_pcre > 0 && rule_pcre_http == 0*/) {
1220 fprintf(rule_engine_analysis_FD, " Warning: Rule uses content options with http_* and pcre options without http modifiers.\n"
1221 " -Consider adding http pcre modifier.\n");
1222 }
1223 else if (warn_pcre_http /*s->alproto == ALPROTO_HTTP && rule_pcre > 0 && rule_pcre_http == 0*/) {
1224 fprintf(rule_engine_analysis_FD, " Warning: Rule app layer protocol is http, but pcre options do not have http modifiers.\n"
1225 " -Consider adding http pcre modifiers.\n");
1226 }
1227 if (warn_content_http_content /*rule_content > 0 && rule_content_http > 0*/) {
1228 fprintf(rule_engine_analysis_FD, " Warning: Rule contains content with http_* and content without http_*.\n"
1229 " -Consider adding http content modifiers.\n");
1230 }
1231 if (warn_content_http /*s->alproto == ALPROTO_HTTP && rule_content > 0 && rule_content_http == 0*/) {
1232 fprintf(rule_engine_analysis_FD, " Warning: Rule app layer protocol is http, but content options do not have http_* modifiers.\n"
1233 " -Consider adding http content modifiers.\n");
1234 }
1235 if (rule_content == 1) {
1236 //todo: warning if content is weak, separate warning for pcre + weak content
1237 }
1238 if (warn_encoding_norm_http_buf) {
1239 fprintf(rule_engine_analysis_FD, " Warning: Rule may contain percent encoded content for a normalized http buffer match.\n");
1240 }
1241 if (warn_tcp_no_flow /*rule_flow == 0 && rule_flags == 0
1242 && !(s->proto.flags & DETECT_PROTO_ANY) && DetectProtoContainsProto(&s->proto, IPPROTO_TCP)*/) {
1243 fprintf(rule_engine_analysis_FD, " Warning: TCP rule without a flow or flags option.\n"
1244 " -Consider adding flow or flags to improve performance of this rule.\n");
1245 }
1246 if (warn_client_ports /*rule_flow && !rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)
1247 && !((s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY)))
1248 if (((s->flags & SIG_FLAG_TOSERVER) && !(s->flags & SIG_FLAG_SP_ANY) && (s->flags & SIG_FLAG_DP_ANY))
1249 || ((s->flags & SIG_FLAG_TOCLIENT) && !(s->flags & SIG_FLAG_DP_ANY) && (s->flags & SIG_FLAG_SP_ANY))*/) {
1250 fprintf(rule_engine_analysis_FD, " Warning: Rule contains ports or port variables only on the client side.\n"
1251 " -Flow direction possibly inconsistent with rule.\n");
1252 }
1253 if (warn_direction /*rule_flow && rule_bidirectional && (rule_flow_toserver || rule_flow_toclient)*/) {
1254 fprintf(rule_engine_analysis_FD, " Warning: Rule is bidirectional and has a flow option with a specific direction.\n");
1255 }
1256 if (warn_method_toclient /*http_method_buf && rule_flow && rule_flow_toclient*/) {
1257 fprintf(rule_engine_analysis_FD, " Warning: Rule uses content or pcre for http_method with flow:to_client or from_server\n");
1258 }
1259 if (warn_method_serverbody /*http_method_buf && http_server_body_buf*/) {
1260 fprintf(rule_engine_analysis_FD, " Warning: Rule uses content or pcre for http_method with content or pcre for http_server_body.\n");
1261 }
1262 if (warn_pcre_method /*http_method_buf && rule_content == 0 && rule_content_http == 0
1263 && (rule_pcre > 0 || rule_pcre_http > 0)*/) {
1264 fprintf(rule_engine_analysis_FD, " Warning: Rule uses pcre with only a http_method content; possible performance issue.\n");
1265 }
1266 if (warn_offset_depth_pkt_stream) {
1267 fprintf(rule_engine_analysis_FD, " Warning: Rule has depth"
1268 "/offset with raw content keywords. Please note the "
1269 "offset/depth will be checked against both packet "
1270 "payloads and stream. If you meant to have the offset/"
1271 "depth checked against just the payload, you can update "
1272 "the signature as \"alert tcp-pkt...\"\n");
1273 }
1274 if (warn_offset_depth_alproto) {
1275 fprintf(rule_engine_analysis_FD, " Warning: Rule has "
1276 "offset/depth set along with a match on a specific "
1277 "app layer protocol - %d. This can lead to FNs if we "
1278 "have a offset/depth content match on a packet payload "
1279 "before we can detect the app layer protocol for the "
1280 "flow.\n", s->alproto);
1281 }
1282 if (warn_non_alproto_fp_for_alproto_sig) {
1283 fprintf(rule_engine_analysis_FD, " Warning: Rule app layer "
1284 "protocol is http, but the fast_pattern is set on the raw "
1285 "stream. Consider adding fast_pattern over a http "
1286 "buffer for increased performance.");
1287 }
1288 if (warn_no_direction) {
1289 fprintf(rule_engine_analysis_FD, " Warning: Rule has no direction indicator.\n");
1290 }
1291 if (warn_both_direction) {
1292 fprintf(rule_engine_analysis_FD, " Warning: Rule is inspecting both the request and the response.\n");
1293 }
1294 if (warn_file_store_not_present) {
1295 fprintf(rule_engine_analysis_FD, " Warning: Rule requires file-store but the output file-store is not enabled.\n");
1296 }
1297 if (rule_warning == 0) {
1298 fprintf(rule_engine_analysis_FD, " No warnings for this rule.\n");
1299 }
1300 fprintf(rule_engine_analysis_FD, "\n");
1301 }
1302 return;
1303 }
1304