suricata
util-spm-hs.c
Go to the documentation of this file.
1 /* Copyright (C) 2016-2023 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 Justin Viiret <justin.viiret@intel.com>
22  *
23  * Single pattern matcher that uses the Hyperscan regex matcher.
24  */
25 
26 #include "suricata-common.h"
27 #include "util-hyperscan.h"
28 #include "util-spm.h"
29 #include "util-spm-hs.h"
30 #include "util-debug.h"
31 #include "util-validate.h"
32 
33 #ifdef BUILD_HYPERSCAN
34 
35 #include <hs.h>
36 
37 /**
38  * \internal
39  * \brief Hyperscan match callback, called by hs_scan.
40  */
41 static int MatchEvent(unsigned int id, unsigned long long from,
42  unsigned long long to, unsigned int flags, void *context)
43 {
44  uint64_t *match_offset = context;
45  DEBUG_VALIDATE_BUG_ON(*match_offset != UINT64_MAX);
46  *match_offset = to;
47  return 1; /* Terminate matching. */
48 }
49 
50 typedef struct SpmHsCtx_ {
51  hs_database_t *db;
52  uint16_t needle_len;
53 } SpmHsCtx;
54 
55 static void HSDestroyCtx(SpmCtx *ctx)
56 {
57  if (ctx == NULL) {
58  return;
59  }
60  SpmHsCtx *sctx = ctx->ctx;
61  if (sctx) {
62  hs_free_database(sctx->db);
63  SCFree(sctx);
64  }
65  SCFree(ctx);
66 }
67 
68 static int HSBuildDatabase(const uint8_t *needle, uint16_t needle_len,
69  int nocase, SpmHsCtx *sctx,
70  SpmGlobalThreadCtx *global_thread_ctx)
71 {
72  char *expr = HSRenderPattern(needle, needle_len);
73  if (expr == NULL) {
74  SCLogDebug("HSRenderPattern returned NULL");
75  return -1;
76  }
77 
78  unsigned flags = nocase ? HS_FLAG_CASELESS : 0;
79 
80  hs_database_t *db = NULL;
81  hs_compile_error_t *compile_err = NULL;
82  hs_error_t err = hs_compile(expr, flags, HS_MODE_BLOCK, NULL, &db,
83  &compile_err);
84  if (err != HS_SUCCESS) {
85  SCLogError("Unable to compile '%s' with Hyperscan, "
86  "returned %d.",
87  expr, err);
88  return -1;
89  }
90 
91  SCFree(expr);
92 
93  /* Update scratch for this database. */
94  hs_scratch_t *scratch = global_thread_ctx->ctx;
95  err = hs_alloc_scratch(db, &scratch);
96  if (err != HS_SUCCESS) {
97  /* If scratch allocation failed, this is not recoverable: other SPM
98  * contexts may need this scratch space. */
99  SCLogError("Unable to alloc scratch for Hyperscan, returned %d.", err);
100  return -1;
101  }
102  global_thread_ctx->ctx = scratch;
103  sctx->db = db;
104  sctx->needle_len = needle_len;
105 
106  return 0;
107 }
108 
109 static SpmCtx *HSInitCtx(const uint8_t *needle, uint16_t needle_len, int nocase,
110  SpmGlobalThreadCtx *global_thread_ctx)
111 {
112  SpmCtx *ctx = SCCalloc(1, sizeof(SpmCtx));
113  if (ctx == NULL) {
114  SCLogDebug("Unable to alloc SpmCtx.");
115  return NULL;
116  }
117  ctx->matcher = SPM_HS;
118 
119  SpmHsCtx *sctx = SCCalloc(1, sizeof(SpmHsCtx));
120  if (sctx == NULL) {
121  SCLogDebug("Unable to alloc SpmHsCtx.");
122  SCFree(ctx);
123  return NULL;
124  }
125  ctx->ctx = sctx;
126 
127  if (HSBuildDatabase(needle, needle_len, nocase, sctx,
128  global_thread_ctx) != 0) {
129  SCLogDebug("HSBuildDatabase failed.");
130  HSDestroyCtx(ctx);
131  return NULL;
132  }
133 
134  return ctx;
135 }
136 
137 static uint8_t *HSScan(const SpmCtx *ctx, SpmThreadCtx *thread_ctx,
138  const uint8_t *haystack, uint32_t haystack_len)
139 {
140  const SpmHsCtx *sctx = ctx->ctx;
141  hs_scratch_t *scratch = thread_ctx->ctx;
142 
143  if (unlikely(haystack_len == 0)) {
144  return NULL;
145  }
146 
147  uint64_t match_offset = UINT64_MAX;
148  hs_error_t err = hs_scan(sctx->db, (const char *)haystack, haystack_len, 0,
149  scratch, MatchEvent, &match_offset);
150  if (err != HS_SUCCESS && err != HS_SCAN_TERMINATED) {
151  /* An error value (other than HS_SCAN_TERMINATED) from hs_scan()
152  * indicates that it was passed an invalid database or scratch region,
153  * which is not something we can recover from at scan time. */
154  SCLogError("Hyperscan returned fatal error %d.", err);
155  exit(EXIT_FAILURE);
156  }
157 
158  if (match_offset == UINT64_MAX) {
159  return NULL;
160  }
161 
162  DEBUG_VALIDATE_BUG_ON(match_offset < sctx->needle_len);
163 
164  /* Note: existing API returns non-const ptr */
165  return (uint8_t *)haystack + (match_offset - sctx->needle_len);
166 }
167 
168 static SpmGlobalThreadCtx *HSInitGlobalThreadCtx(void)
169 {
170  SpmGlobalThreadCtx *global_thread_ctx = SCCalloc(1, sizeof(SpmGlobalThreadCtx));
171  if (global_thread_ctx == NULL) {
172  SCLogDebug("Unable to alloc SpmGlobalThreadCtx.");
173  return NULL;
174  }
175  global_thread_ctx->matcher = SPM_HS;
176 
177  /* We store scratch in the HS-specific ctx. This will be initialized as
178  * patterns are compiled by SpmInitCtx. */
179  global_thread_ctx->ctx = NULL;
180 
181  return global_thread_ctx;
182 }
183 
184 static void HSDestroyGlobalThreadCtx(SpmGlobalThreadCtx *global_thread_ctx)
185 {
186  if (global_thread_ctx == NULL) {
187  return;
188  }
189  hs_free_scratch(global_thread_ctx->ctx);
190  SCFree(global_thread_ctx);
191 }
192 
193 static void HSDestroyThreadCtx(SpmThreadCtx *thread_ctx)
194 {
195  if (thread_ctx == NULL) {
196  return;
197  }
198  hs_free_scratch(thread_ctx->ctx);
199  SCFree(thread_ctx);
200 }
201 
202 static SpmThreadCtx *HSMakeThreadCtx(const SpmGlobalThreadCtx *global_thread_ctx)
203 {
204  SpmThreadCtx *thread_ctx = SCCalloc(1, sizeof(SpmThreadCtx));
205  if (thread_ctx == NULL) {
206  SCLogDebug("Unable to alloc SpmThreadCtx.");
207  return NULL;
208  }
209  thread_ctx->matcher = SPM_HS;
210 
211  if (global_thread_ctx->ctx != NULL) {
212  hs_scratch_t *scratch = NULL;
213  hs_error_t err = hs_clone_scratch(global_thread_ctx->ctx, &scratch);
214  if (err != HS_SUCCESS) {
215  SCLogError("Unable to clone scratch (error %d).", err);
216  exit(EXIT_FAILURE);
217  }
218  thread_ctx->ctx = scratch;
219  }
220 
221  return thread_ctx;
222 }
223 
224 void SpmHSRegister(void)
225 {
226  spm_table[SPM_HS].name = "hs";
227  spm_table[SPM_HS].InitGlobalThreadCtx = HSInitGlobalThreadCtx;
228  spm_table[SPM_HS].DestroyGlobalThreadCtx = HSDestroyGlobalThreadCtx;
229  spm_table[SPM_HS].MakeThreadCtx = HSMakeThreadCtx;
230  spm_table[SPM_HS].DestroyThreadCtx = HSDestroyThreadCtx;
231  spm_table[SPM_HS].InitCtx = HSInitCtx;
232  spm_table[SPM_HS].DestroyCtx = HSDestroyCtx;
233  spm_table[SPM_HS].Scan = HSScan;
234 }
235 
236 #endif /* BUILD_HYPERSCAN */
SpmThreadCtx_::ctx
void * ctx
Definition: util-spm.h:56
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
SpmTableElmt_::Scan
uint8_t *(* Scan)(const SpmCtx *ctx, SpmThreadCtx *thread_ctx, const uint8_t *haystack, uint32_t haystack_len)
Definition: util-spm.h:68
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:275
SpmTableElmt_::InitCtx
SpmCtx *(* InitCtx)(const uint8_t *needle, uint16_t needle_len, int nocase, SpmGlobalThreadCtx *g_thread_ctx)
Definition: util-spm.h:65
ctx
struct Thresholds ctx
SpmTableElmt_::name
const char * name
Definition: util-spm.h:60
SpmTableElmt_::MakeThreadCtx
SpmThreadCtx *(* MakeThreadCtx)(const SpmGlobalThreadCtx *g_thread_ctx)
Definition: util-spm.h:63
util-hyperscan.h
util-debug.h
SPM_HS
@ SPM_HS
Definition: util-spm.h:31
SpmTableElmt_::InitGlobalThreadCtx
SpmGlobalThreadCtx *(* InitGlobalThreadCtx)(void)
Definition: util-spm.h:61
SpmGlobalThreadCtx_::matcher
uint8_t matcher
Definition: util-spm.h:48
SpmCtx_
Definition: util-spm.h:40
flags
uint8_t flags
Definition: decode-gre.h:0
suricata-common.h
SpmTableElmt_::DestroyCtx
void(* DestroyCtx)(SpmCtx *)
Definition: util-spm.h:67
util-spm.h
SpmGlobalThreadCtx_::ctx
void * ctx
Definition: util-spm.h:49
HSRenderPattern
char * HSRenderPattern(const uint8_t *pat, uint16_t pat_len)
util-spm-hs.h
util-validate.h
SpmGlobalThreadCtx_
Definition: util-spm.h:47
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:267
SCFree
#define SCFree(p)
Definition: util-mem.h:61
spm_table
SpmTableElmt spm_table[SPM_TABLE_SIZE]
Definition: util-spm.c:62
SpmHSRegister
void SpmHSRegister(void)
SpmThreadCtx_::matcher
uint8_t matcher
Definition: util-spm.h:55
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
DEBUG_VALIDATE_BUG_ON
#define DEBUG_VALIDATE_BUG_ON(exp)
Definition: util-validate.h:102
SpmTableElmt_::DestroyThreadCtx
void(* DestroyThreadCtx)(SpmThreadCtx *thread_ctx)
Definition: util-spm.h:64
SpmTableElmt_::DestroyGlobalThreadCtx
void(* DestroyGlobalThreadCtx)(SpmGlobalThreadCtx *g_thread_ctx)
Definition: util-spm.h:62
SpmThreadCtx_
Definition: util-spm.h:54