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  rewind(file);
83  size_t bytes_read = fread(buffer, 1, file_sz, file);
84  if (bytes_read != (size_t)file_sz) {
85  SCLogDebug("Failed to read the entire file %s: %s", file_path, strerror(errno));
86  SCFree(buffer);
87  fclose(file);
88  return NULL;
89  }
90 
91  *buffer_sz = file_sz;
92  fclose(file);
93  return buffer;
94 }
95 
96 /**
97  * Function to hash the searched pattern, only things relevant to Hyperscan
98  * compilation are hashed.
99  */
100 static void SCHSCachePatternHash(const SCHSPattern *p, uint32_t *h1, uint32_t *h2)
101 {
102  BUG_ON(p->original_pat == NULL);
103  BUG_ON(p->sids == NULL);
104 
105  hashlittle2_safe(&p->len, sizeof(p->len), h1, h2);
106  hashlittle2_safe(&p->flags, sizeof(p->flags), h1, h2);
107  hashlittle2_safe(p->original_pat, p->len, h1, h2);
108  hashlittle2_safe(&p->id, sizeof(p->id), h1, h2);
109  hashlittle2_safe(&p->offset, sizeof(p->offset), h1, h2);
110  hashlittle2_safe(&p->depth, sizeof(p->depth), h1, h2);
111  hashlittle2_safe(&p->sids_size, sizeof(p->sids_size), h1, h2);
112  hashlittle2_safe(p->sids, p->sids_size * sizeof(SigIntId), h1, h2);
113 }
114 
115 int HSLoadCache(hs_database_t **hs_db, uint64_t hs_db_hash, const char *dirpath)
116 {
117  const char *hash_file_static = HSCacheConstructFPath(dirpath, hs_db_hash);
118  if (hash_file_static == NULL)
119  return -1;
120 
121  SCLogDebug("Loading the cached HS DB from %s", hash_file_static);
122  if (!SCPathExists(hash_file_static))
123  return -1;
124 
125  FILE *db_cache = fopen(hash_file_static, "r");
126  char *buffer = NULL;
127  int ret = 0;
128  if (db_cache) {
129  size_t buffer_size;
130  buffer = HSReadStream(hash_file_static, &buffer_size);
131  if (!buffer) {
132  SCLogWarning("Hyperscan cached DB file %s cannot be read", hash_file_static);
133  ret = -1;
134  goto freeup;
135  }
136 
137  hs_error_t error = hs_deserialize_database(buffer, buffer_size, hs_db);
138  if (error != HS_SUCCESS) {
139  SCLogWarning("Failed to deserialize Hyperscan database of %s: %s", hash_file_static,
140  HSErrorToStr(error));
141  ret = -1;
142  goto freeup;
143  }
144 
145  ret = 0;
146  goto freeup;
147  }
148 
149 freeup:
150  if (db_cache)
151  fclose(db_cache);
152  if (buffer)
153  SCFree(buffer);
154  return ret;
155 }
156 
157 static int HSSaveCache(hs_database_t *hs_db, uint64_t hs_db_hash, const char *dstpath)
158 {
159  static bool notified = false;
160  char *db_stream = NULL;
161  size_t db_size;
162  int ret = -1;
163 
164  hs_error_t err = hs_serialize_database(hs_db, &db_stream, &db_size);
165  if (err != HS_SUCCESS) {
166  SCLogWarning("Failed to serialize Hyperscan database: %s", HSErrorToStr(err));
167  goto cleanup;
168  }
169 
170  const char *hash_file_static = HSCacheConstructFPath(dstpath, hs_db_hash);
171  SCLogDebug("Caching the compiled HS at %s", hash_file_static);
172  if (SCPathExists(hash_file_static)) {
173  // potentially signs that it might not work as expected as we got into
174  // hash collision. If this happens with older and not used caches it is
175  // fine.
176  // It is problematic when one ruleset yields two colliding MPM groups.
177  SCLogWarning("Overwriting cache file %s. If the problem persists consider switching off "
178  "the caching",
179  hash_file_static);
180  }
181 
182  FILE *db_cache_out = fopen(hash_file_static, "w");
183  if (!db_cache_out) {
184  if (!notified) {
185  SCLogWarning("Failed to create Hyperscan cache file, make sure the folder exist and is "
186  "writable or adjust sgh-mpm-caching-path setting (%s)",
187  hash_file_static);
188  notified = true;
189  }
190  goto cleanup;
191  }
192  size_t r = fwrite(db_stream, sizeof(db_stream[0]), db_size, db_cache_out);
193  if (r > 0 && (size_t)r != db_size) {
194  SCLogWarning("Failed to write to file: %s", hash_file_static);
195  if (r != db_size) {
196  // possibly a corrupted DB cache was created
197  r = remove(hash_file_static);
198  if (r != 0) {
199  SCLogWarning("Failed to remove corrupted cache file: %s", hash_file_static);
200  }
201  }
202  }
203  ret = fclose(db_cache_out);
204  if (ret != 0) {
205  SCLogWarning("Failed to close file: %s", hash_file_static);
206  goto cleanup;
207  }
208 
209  ret = 0;
210 cleanup:
211  if (db_stream)
212  SCFree(db_stream);
213  return ret;
214 }
215 
216 uint64_t HSHashDb(const PatternDatabase *pd)
217 {
218  uint64_t cached_hash = 0;
219  uint32_t *hash = (uint32_t *)(&cached_hash);
220  hashword2(&pd->pattern_cnt, 1, &hash[0], &hash[1]);
221  for (uint32_t i = 0; i < pd->pattern_cnt; i++) {
222  SCHSCachePatternHash(pd->parray[i], &hash[0], &hash[1]);
223  }
224 
225  return cached_hash;
226 }
227 
228 void HSSaveCacheIterator(void *data, void *aux)
229 {
230  PatternDatabase *pd = (PatternDatabase *)data;
231  struct HsIteratorData *iter_data = (struct HsIteratorData *)aux;
232  if (pd->no_cache)
233  return;
234 
235  // count only cacheable DBs
236  iter_data->pd_stats->hs_cacheable_dbs_cnt++;
237  if (pd->cached) {
238  iter_data->pd_stats->hs_dbs_cache_loaded_cnt++;
239  return;
240  }
241 
242  if (HSSaveCache(pd->hs_db, HSHashDb(pd), iter_data->cache_path) == 0) {
243  pd->cached = true; // for rule reloads
244  iter_data->pd_stats->hs_dbs_cache_saved_cnt++;
245  }
246 }
247 
248 #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:309
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:324
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