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 
39 static 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 =
46  snprintf(filename, sizeof(filename), "%020lu%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 
57 static 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  */
107 static 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 
122 int 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 
156 freeup:
157  if (db_cache)
158  fclose(db_cache);
159  if (buffer)
160  SCFree(buffer);
161  return ret;
162 }
163 
164 static 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;
217 cleanup:
218  if (db_stream)
219  SCFree(db_stream);
220  return ret;
221 }
222 
223 uint64_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 
235 void 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 */
detect-engine.h
PathMerge
int PathMerge(char *out_buf, size_t buf_size, const char *const dir, const char *const fname)
Definition: util-path.c:74
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:269
hashlittle2_safe
void hashlittle2_safe(const void *key, size_t length, uint32_t *pc, uint32_t *pb)
Definition: util-hash-lookup3.c:818
util-debug.h
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:249
BUG_ON
#define BUG_ON(x)
Definition: suricata-common.h:317
SCPathExists
bool SCPathExists(const char *path)
Check if a path exists.
Definition: util-path.c:183
suricata-common.h
util-path.h
util-mpm-hs-core.h
util-hash-lookup3.h
SCFree
#define SCFree(p)
Definition: util-mem.h:61
suricata.h
util-mpm-hs-cache.h
SigIntId
#define SigIntId
Definition: suricata-common.h:332
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
hashword2
void hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb)
Definition: util-hash-lookup3.c:216