suricata
util-mpm-hs-cache.c
Go to the documentation of this file.
1/* Copyright (C) 2007-2024 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 Lukas Sismis <lsismis@oisf.net>
22 *
23 * MPM pattern matcher that calls the Hyperscan regex matcher.
24 */
25
26#include "suricata-common.h"
27#include "suricata.h"
28#include "detect-engine.h"
29#include "util-debug.h"
30#include "util-hash-lookup3.h"
31#include "util-mpm-hs-core.h"
32#include "util-mpm-hs-cache.h"
33#include "util-path.h"
34
35#ifdef BUILD_HYPERSCAN
36
37#include <hs.h>
38
39static const char *HSCacheConstructFPath(const char *folder_path, uint64_t hs_db_hash)
40{
41 static char hash_file_path[PATH_MAX];
42
43 char hash_file_path_suffix[] = "_v1.hs";
44 char filename[PATH_MAX];
45 uint64_t r = snprintf(
46 filename, sizeof(filename), "%020" PRIu64 "%s", hs_db_hash, hash_file_path_suffix);
47 if (r != (uint64_t)(20 + strlen(hash_file_path_suffix)))
48 return NULL;
49
50 r = PathMerge(hash_file_path, sizeof(hash_file_path), folder_path, filename);
51 if (r)
52 return NULL;
53
54 return hash_file_path;
55}
56
57static char *HSReadStream(const char *file_path, size_t *buffer_sz)
58{
59 FILE *file = fopen(file_path, "rb");
60 if (!file) {
61 SCLogDebug("Failed to open file %s: %s", file_path, strerror(errno));
62 return NULL;
63 }
64
65 // Seek to the end of the file to determine its size
66 fseek(file, 0, SEEK_END);
67 long file_sz = ftell(file);
68 if (file_sz < 0) {
69 SCLogDebug("Failed to determine file size of %s: %s", file_path, strerror(errno));
70 fclose(file);
71 return NULL;
72 }
73
74 char *buffer = (char *)SCCalloc(file_sz, sizeof(char));
75 if (!buffer) {
76 SCLogWarning("Failed to allocate memory");
77 fclose(file);
78 return NULL;
79 }
80
81 // Rewind file pointer and read the file into the buffer
82 errno = 0;
83 rewind(file);
84 if (errno != 0) {
85 SCLogDebug("Failed to rewind file %s: %s", file_path, strerror(errno));
86 SCFree(buffer);
87 fclose(file);
88 return NULL;
89 }
90 size_t bytes_read = fread(buffer, 1, file_sz, file);
91 if (bytes_read != (size_t)file_sz) {
92 SCLogDebug("Failed to read the entire file %s: %s", file_path, strerror(errno));
93 SCFree(buffer);
94 fclose(file);
95 return NULL;
96 }
97
98 *buffer_sz = file_sz;
99 fclose(file);
100 return buffer;
101}
102
103/**
104 * Function to hash the searched pattern, only things relevant to Hyperscan
105 * compilation are hashed.
106 */
107static void SCHSCachePatternHash(const SCHSPattern *p, uint32_t *h1, uint32_t *h2)
108{
109 BUG_ON(p->original_pat == NULL);
110 BUG_ON(p->sids == NULL);
111
112 hashlittle2_safe(&p->len, sizeof(p->len), h1, h2);
113 hashlittle2_safe(&p->flags, sizeof(p->flags), h1, h2);
114 hashlittle2_safe(p->original_pat, p->len, h1, h2);
115 hashlittle2_safe(&p->id, sizeof(p->id), h1, h2);
116 hashlittle2_safe(&p->offset, sizeof(p->offset), h1, h2);
117 hashlittle2_safe(&p->depth, sizeof(p->depth), h1, h2);
118 hashlittle2_safe(&p->sids_size, sizeof(p->sids_size), h1, h2);
119 hashlittle2_safe(p->sids, p->sids_size * sizeof(SigIntId), h1, h2);
120}
121
122int HSLoadCache(hs_database_t **hs_db, uint64_t hs_db_hash, const char *dirpath)
123{
124 const char *hash_file_static = HSCacheConstructFPath(dirpath, hs_db_hash);
125 if (hash_file_static == NULL)
126 return -1;
127
128 SCLogDebug("Loading the cached HS DB from %s", hash_file_static);
129 if (!SCPathExists(hash_file_static))
130 return -1;
131
132 FILE *db_cache = fopen(hash_file_static, "r");
133 char *buffer = NULL;
134 int ret = 0;
135 if (db_cache) {
136 size_t buffer_size;
137 buffer = HSReadStream(hash_file_static, &buffer_size);
138 if (!buffer) {
139 SCLogWarning("Hyperscan cached DB file %s cannot be read", hash_file_static);
140 ret = -1;
141 goto freeup;
142 }
143
144 hs_error_t error = hs_deserialize_database(buffer, buffer_size, hs_db);
145 if (error != HS_SUCCESS) {
146 SCLogWarning("Failed to deserialize Hyperscan database of %s: %s", hash_file_static,
147 HSErrorToStr(error));
148 ret = -1;
149 goto freeup;
150 }
151
152 ret = 0;
153 goto freeup;
154 }
155
156freeup:
157 if (db_cache)
158 fclose(db_cache);
159 if (buffer)
160 SCFree(buffer);
161 return ret;
162}
163
164static int HSSaveCache(hs_database_t *hs_db, uint64_t hs_db_hash, const char *dstpath)
165{
166 static bool notified = false;
167 char *db_stream = NULL;
168 size_t db_size;
169 int ret = -1;
170
171 hs_error_t err = hs_serialize_database(hs_db, &db_stream, &db_size);
172 if (err != HS_SUCCESS) {
173 SCLogWarning("Failed to serialize Hyperscan database: %s", HSErrorToStr(err));
174 goto cleanup;
175 }
176
177 const char *hash_file_static = HSCacheConstructFPath(dstpath, hs_db_hash);
178 SCLogDebug("Caching the compiled HS at %s", hash_file_static);
179 if (SCPathExists(hash_file_static)) {
180 // potentially signs that it might not work as expected as we got into
181 // hash collision. If this happens with older and not used caches it is
182 // fine.
183 // It is problematic when one ruleset yields two colliding MPM groups.
184 SCLogWarning("Overwriting cache file %s. If the problem persists consider switching off "
185 "the caching",
186 hash_file_static);
187 }
188
189 FILE *db_cache_out = fopen(hash_file_static, "w");
190 if (!db_cache_out) {
191 if (!notified) {
192 SCLogWarning("Failed to create Hyperscan cache file, make sure the folder exist and is "
193 "writable or adjust sgh-mpm-caching-path setting (%s)",
194 hash_file_static);
195 notified = true;
196 }
197 goto cleanup;
198 }
199 size_t r = fwrite(db_stream, sizeof(db_stream[0]), db_size, db_cache_out);
200 if (r > 0 && (size_t)r != db_size) {
201 SCLogWarning("Failed to write to file: %s", hash_file_static);
202 if (r != db_size) {
203 // possibly a corrupted DB cache was created
204 r = remove(hash_file_static);
205 if (r != 0) {
206 SCLogWarning("Failed to remove corrupted cache file: %s", hash_file_static);
207 }
208 }
209 }
210 ret = fclose(db_cache_out);
211 if (ret != 0) {
212 SCLogWarning("Failed to close file: %s", hash_file_static);
213 goto cleanup;
214 }
215
216 ret = 0;
217cleanup:
218 if (db_stream)
219 SCFree(db_stream);
220 return ret;
221}
222
223uint64_t HSHashDb(const PatternDatabase *pd)
224{
225 uint64_t cached_hash = 0;
226 uint32_t *hash = (uint32_t *)(&cached_hash);
227 hashword2(&pd->pattern_cnt, 1, &hash[0], &hash[1]);
228 for (uint32_t i = 0; i < pd->pattern_cnt; i++) {
229 SCHSCachePatternHash(pd->parray[i], &hash[0], &hash[1]);
230 }
231
232 return cached_hash;
233}
234
235void HSSaveCacheIterator(void *data, void *aux)
236{
237 PatternDatabase *pd = (PatternDatabase *)data;
238 struct HsIteratorData *iter_data = (struct HsIteratorData *)aux;
239 if (pd->no_cache)
240 return;
241
242 // count only cacheable DBs
243 iter_data->pd_stats->hs_cacheable_dbs_cnt++;
244 if (pd->cached) {
245 iter_data->pd_stats->hs_dbs_cache_loaded_cnt++;
246 return;
247 }
248
249 if (HSSaveCache(pd->hs_db, HSHashDb(pd), iter_data->cache_path) == 0) {
250 pd->cached = true; // for rule reloads
251 iter_data->pd_stats->hs_dbs_cache_saved_cnt++;
252 }
253}
254
255#endif /* BUILD_HYPERSCAN */
#define BUG_ON(x)
#define SigIntId
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition util-debug.h:255
void hashlittle2_safe(const void *key, size_t length, uint32_t *pc, uint32_t *pb)
void hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb)
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
int PathMerge(char *out_buf, size_t buf_size, const char *const dir, const char *const fname)
Definition util-path.c:74
bool SCPathExists(const char *path)
Check if a path exists.
Definition util-path.c:183