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 */
41static 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
50typedef struct SpmHsCtx_ {
51 hs_database_t *db;
52 uint16_t needle_len;
53} SpmHsCtx;
54
55static 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
68static 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
109static 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
137static 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
168static 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
184static 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
193static 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
202static 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
224void 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 */
uint8_t flags
Definition decode-gre.h:0
struct Thresholds ctx
SpmCtx *(* InitCtx)(const uint8_t *needle, uint16_t needle_len, int nocase, SpmGlobalThreadCtx *g_thread_ctx)
Definition util-spm.h:65
SpmGlobalThreadCtx *(* InitGlobalThreadCtx)(void)
Definition util-spm.h:61
void(* DestroyGlobalThreadCtx)(SpmGlobalThreadCtx *g_thread_ctx)
Definition util-spm.h:62
void(* DestroyThreadCtx)(SpmThreadCtx *thread_ctx)
Definition util-spm.h:64
const char * name
Definition util-spm.h:60
uint8_t *(* Scan)(const SpmCtx *ctx, SpmThreadCtx *thread_ctx, const uint8_t *haystack, uint32_t haystack_len)
Definition util-spm.h:68
void(* DestroyCtx)(SpmCtx *)
Definition util-spm.h:67
SpmThreadCtx *(* MakeThreadCtx)(const SpmGlobalThreadCtx *g_thread_ctx)
Definition util-spm.h:63
uint8_t matcher
Definition util-spm.h:55
void * ctx
Definition util-spm.h:56
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
char * HSRenderPattern(const uint8_t *pat, uint16_t pat_len)
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define unlikely(expr)
void SpmHSRegister(void)
SpmTableElmt spm_table[SPM_TABLE_SIZE]
Definition util-spm.c:62
@ SPM_HS
Definition util-spm.h:31
#define DEBUG_VALIDATE_BUG_ON(exp)