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