suricata
util-var-name.c
Go to the documentation of this file.
1/* Copyright (C) 2007-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 Victor Julien <victor@inliniac.net>
22 *
23 * Generic variable name utility functions
24 */
25
26#include "suricata-common.h"
27#include "detect.h"
28#include "util-hash-string.h"
29#include "util-hashlist.h"
30#include "util-var-name.h"
31#include "util-validate.h"
32
33/* Overall Design:
34 *
35 * Base Store: "base"
36 *
37 * Used during keyword registration. Operates under lock. Base is shared
38 * between all detect engines, detect engine versions and tenants.
39 * Each variable name is ref counted.
40 *
41 * During the freeing of a detect engine / tenant, unregistration decreases
42 * the ref cnt.
43 *
44 * Base has both a string to id and a id to string hash table. String to
45 * id is used during parsing/registration. id to string during unregistration.
46 *
47 *
48 * Active Store Pointer (atomic)
49 *
50 * The "active" store atomic pointer points to the active store. The call
51 * to `VarNameStoreActivate` will build a new lookup store and hot swap
52 * the pointer.
53 *
54 * Ensuring memory safety. During the hot swap, the pointer is replaced, so
55 * any new call to the lookup functions will automatically use the new store.
56 * This leaves the case of any lookup happening concurrently with the pointer
57 * swap. For this case we add the old store to a free list. It gets a timestamp
58 * before which it cannot be freed.
59 *
60 *
61 * Free List
62 *
63 * The free list contains old stores that are waiting to get removed. They
64 * contain a timestamp that is checked before they are freed.
65 *
66 */
75
76/** \brief Name2idx mapping structure for flowbits, flowvars and pktvars. */
77typedef struct VariableName_ {
78 char *name;
79 enum VarTypes type; /* flowbit, pktvar, etc */
80 uint32_t id;
81 uint32_t ref_cnt;
83
84#define VARNAME_HASHSIZE 0x1000
85#define VARID_HASHSIZE 0x1000
86
87static SCMutex base_lock = SCMUTEX_INITIALIZER;
88static VarNameStore base = { .names = NULL, .ids = NULL, .max_id = 0 };
89static TAILQ_HEAD(, VarNameStore_) free_list = TAILQ_HEAD_INITIALIZER(free_list);
91
92static uint32_t VariableNameHash(HashListTable *ht, void *buf, uint16_t buflen);
93static char VariableNameCompare(void *buf1, uint16_t len1, void *buf2, uint16_t len2);
94static uint32_t VariableIdHash(HashListTable *ht, void *ptr, uint16_t _unused);
95static char VariableIdCompare(void *ptr1, uint16_t _unused1, void *ptr2, uint16_t _unused2);
96static void VariableNameFree(void *data);
97
98void VarNameStoreInit(void)
99{
100 SCMutexLock(&base_lock);
102 VARNAME_HASHSIZE, VariableNameHash, VariableNameCompare, VariableNameFree);
103 if (base.names == NULL) {
104 FatalError("failed to initialize variable name hash (names)");
105 }
106
107 /* base.names owns the allocation, so use a NULL Free pointer here */
108 base.ids = HashListTableInit(VARID_HASHSIZE, VariableIdHash, VariableIdCompare, NULL);
109 if (base.ids == NULL) {
110 FatalError("failed to initialize variable name hash (names)");
111 }
112 SC_ATOMIC_INITPTR(active);
113 SCMutexUnlock(&base_lock);
114}
115
117{
118 SCMutexLock(&base_lock);
119 VarNameStore *s = SC_ATOMIC_GET(active);
120 if (s) {
123 SCFree(s);
124 s = NULL;
125 }
126 SC_ATOMIC_SET(active, NULL);
127
128 while ((s = TAILQ_FIRST(&free_list))) {
129 TAILQ_REMOVE(&free_list, s, next);
132 SCFree(s);
133 }
134
135 for (HashListTableBucket *b = HashListTableGetListHead(base.names); b != NULL;
139 if (vn->ref_cnt > 0) {
140 SCLogWarning("%s (type %u, id %u) still has ref_cnt %u", vn->name, vn->type, vn->id,
141 vn->ref_cnt);
142 }
143 }
145 base.ids = NULL;
147 base.names = NULL;
148 base.max_id = 0;
149 SCMutexUnlock(&base_lock);
150}
151
152/**
153 * \retval id or 0 on error
154 */
155uint32_t VarNameStoreRegister(const char *name, const enum VarTypes type)
156{
157 SCMutexLock(&base_lock);
158 uint32_t id = 0;
159
160 SCLogDebug("registering: name %s type %u", name, type);
161 VariableName lookup = { .type = type, .name = (char *)name };
162 VariableName *found = (VariableName *)HashListTableLookup(base.names, (void *)&lookup, 0);
163 if (found == NULL) {
164 VariableName *vn = SCCalloc(1, sizeof(VariableName));
165 if (likely(vn != NULL)) {
166 vn->type = type;
167 vn->name = SCStrdup(name);
168 if (vn->name != NULL) {
169 vn->ref_cnt = 1;
170 id = vn->id = ++base.max_id;
171 HashListTableAdd(base.names, (void *)vn, 0);
172 HashListTableAdd(base.ids, (void *)vn, 0);
174 "new registration %s id %u type %u -> %u", vn->name, vn->id, vn->type, id);
175 } else {
176 SCFree(vn);
177 }
178 }
179 } else {
180 id = found->id;
181 found->ref_cnt++;
182 SCLogDebug("existing registration %s ref_cnt %u -> %u", name, found->ref_cnt, id);
183 }
184 SCMutexUnlock(&base_lock);
185 return id;
186}
187
188const char *VarNameStoreSetupLookup(const uint32_t id, const enum VarTypes type)
189{
190 const char *name = NULL;
191 SCMutexLock(&base_lock);
192 VariableName lookup = { .type = type, .id = id };
193 VariableName *found = (VariableName *)HashListTableLookup(base.ids, (void *)&lookup, 0);
194 if (found) {
195 name = found->name;
196 }
197 SCMutexUnlock(&base_lock);
198 return name;
199}
200
201void VarNameStoreUnregister(const uint32_t id, const enum VarTypes type)
202{
203 SCMutexLock(&base_lock);
204 VariableName lookup = { .type = type, .id = id };
205 VariableName *found = (VariableName *)HashListTableLookup(base.ids, (void *)&lookup, 0);
206 if (found) {
207 SCLogDebug("found %s ref_cnt %u", found->name, found->ref_cnt);
208 DEBUG_VALIDATE_BUG_ON(found->ref_cnt == 0);
209 found->ref_cnt--;
210 }
211 SCMutexUnlock(&base_lock);
212}
213
215{
216 int result = 0;
217 SCMutexLock(&base_lock);
218 SCLogDebug("activating new lookup store");
219
220 VarNameStore *new_active = NULL;
221
222 // create lookup hash for id to string, strings should point to base
223 for (HashListTableBucket *b = HashListTableGetListHead(base.names); b != NULL;
226 BUG_ON(vn == NULL);
227 SCLogDebug("base: %s/%u/%u", vn->name, vn->id, vn->ref_cnt);
228 if (vn->ref_cnt == 0)
229 continue;
230
231 if (new_active == NULL) {
232 new_active = SCCalloc(1, sizeof(*new_active));
233 if (new_active == NULL) {
234 result = -1;
235 goto out;
236 }
237
238 new_active->names = HashListTableInit(
239 VARNAME_HASHSIZE, VariableNameHash, VariableNameCompare, NULL);
240 if (new_active->names == NULL) {
241 SCFree(new_active);
242 result = -1;
243 goto out;
244 }
245 new_active->ids =
246 HashListTableInit(VARID_HASHSIZE, VariableIdHash, VariableIdCompare, NULL);
247 if (new_active->ids == NULL) {
248 HashListTableFree(new_active->names);
249 SCFree(new_active);
250 result = -1;
251 goto out;
252 }
253 }
254
255 /* memory is still owned by "base" */
256 HashListTableAdd(new_active->names, (void *)vn, 0);
257 HashListTableAdd(new_active->ids, (void *)vn, 0);
258 }
259
260 if (new_active) {
261 SCTime_t now = SCTimeGetTime();
262
263 VarNameStore *old_active = SC_ATOMIC_GET(active);
264 if (old_active) {
265 SCTime_t free_after = SCTIME_ADD_SECS(now, 60);
266 old_active->free_after = free_after;
267
268 TAILQ_INSERT_TAIL(&free_list, old_active, next);
269 SCLogDebug("old active is stored in free list");
270 }
271
272 SC_ATOMIC_SET(active, new_active);
273 SCLogDebug("new store active");
274
275 VarNameStore *s = NULL;
276 while ((s = TAILQ_FIRST(&free_list))) {
277 char timebuf[64];
278 CreateIsoTimeString(s->free_after, timebuf, sizeof(timebuf));
279
280 if (SCTIME_CMP_LTE(now, s->free_after)) {
281 SCLogDebug("not yet freeing store %p before %s", s, timebuf);
282 break;
283 }
284 SCLogDebug("freeing store %p with time %s", s, timebuf);
285 TAILQ_REMOVE(&free_list, s, next);
288 SCFree(s);
289 }
290 }
291out:
292 SCLogDebug("activating new lookup store: complete %d", result);
293 SCMutexUnlock(&base_lock);
294 return result;
295}
296
297/** \brief find name for id+type at packet time.
298 * As the `active` store won't be modified, we don't need locks. */
299const char *VarNameStoreLookupById(const uint32_t id, const enum VarTypes type)
300{
301 const char *name = NULL;
302
303 /* coverity[missing_lock] */
304 const VarNameStore *current = SC_ATOMIC_GET(active);
305 if (current) {
306 VariableName lookup = { .type = type, .id = id };
307 /* coverity[missing_lock] */
308 const VariableName *found = HashListTableLookup(current->ids, (void *)&lookup, 0);
309 if (found) {
310 return found->name;
311 }
312 }
313
314 return name;
315}
316
317/** \brief find name for id+type at packet time.
318 * As the `active` store won't be modified, we don't need locks. */
319uint32_t VarNameStoreLookupByName(const char *name, const enum VarTypes type)
320{
321 /* coverity[missing_lock] */
322 const VarNameStore *current = SC_ATOMIC_GET(active);
323 if (current) {
324 VariableName lookup = { .name = (char *)name, .type = type };
325 /* coverity[missing_lock] */
326 const VariableName *found = HashListTableLookup(current->names, (void *)&lookup, 0);
327 if (found) {
328 return found->id;
329 }
330 }
331
332 return 0;
333}
334
335static uint32_t VariableNameHash(HashListTable *ht, void *buf, uint16_t buflen)
336{
337 VariableName *vn = (VariableName *)buf;
338 uint32_t hash =
339 StringHashDjb2((const uint8_t *)vn->name, (uint32_t)strlen(vn->name)) + vn->type;
340 return (hash % VARNAME_HASHSIZE);
341}
342
343static char VariableNameCompare(void *buf1, uint16_t len1, void *buf2, uint16_t len2)
344{
345 VariableName *vn1 = (VariableName *)buf1;
346 VariableName *vn2 = (VariableName *)buf2;
347 return (vn1->type == vn2->type && strcmp(vn1->name, vn2->name) == 0);
348}
349
350static uint32_t VariableIdHash(HashListTable *ht, void *ptr, uint16_t _unused)
351{
352 VariableName *vn = (VariableName *)ptr;
353 uint32_t hash = vn->id << vn->type;
354 return (hash % VARID_HASHSIZE);
355}
356
357static char VariableIdCompare(void *ptr1, uint16_t _unused1, void *ptr2, uint16_t _unused2)
358{
359 VariableName *vn1 = (VariableName *)ptr1;
360 VariableName *vn2 = (VariableName *)ptr2;
361
362 return (vn1->id == vn2->id && vn1->type == vn2->type);
363}
364
365static void VariableNameFree(void *data)
366{
367 VariableName *vn = (VariableName *)data;
368 if (vn == NULL)
369 return;
370 if (vn->name != NULL) {
371 SCFree(vn->name);
372 vn->name = NULL;
373 }
374 SCFree(vn);
375}
struct HtpBodyChunk_ * next
uint16_t type
uint32_t id
#define TAILQ_HEAD(name, type)
Definition queue.h:230
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition queue.h:294
#define TAILQ_FIRST(head)
Definition queue.h:250
#define TAILQ_REMOVE(head, elm, field)
Definition queue.h:312
#define TAILQ_HEAD_INITIALIZER(head)
Definition queue.h:236
#define TAILQ_ENTRY(type)
Definition queue.h:239
HashListTable * names
HashListTable * ids
uint32_t max_id
SCTime_t free_after
Name2idx mapping structure for flowbits, flowvars and pktvars.
uint32_t ref_cnt
enum VarTypes type
#define BUG_ON(x)
#define SCMUTEX_INITIALIZER
#define SCMutex
#define SCMutexUnlock(mut)
#define SCMutexLock(mut)
const char * name
#define SC_ATOMIC_INITPTR(name)
#define SC_ATOMIC_DECLARE(type, name)
wrapper for declaring atomic variables.
#define SC_ATOMIC_GET(name)
Get the value from the atomic variable.
#define SC_ATOMIC_SET(name, val)
Set the value for the atomic variable.
#define FatalError(...)
Definition util-debug.h:510
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition util-debug.h:255
uint32_t StringHashDjb2(const uint8_t *data, uint32_t datalen)
void * HashListTableLookup(HashListTable *ht, void *data, uint16_t datalen)
int HashListTableAdd(HashListTable *ht, void *data, uint16_t datalen)
HashListTableBucket * HashListTableGetListHead(HashListTable *ht)
HashListTable * HashListTableInit(uint32_t size, uint32_t(*Hash)(struct HashListTable_ *, void *, uint16_t), char(*Compare)(void *, uint16_t, void *, uint16_t), void(*Free)(void *))
void HashListTableFree(HashListTable *ht)
#define HashListTableGetListData(hb)
#define HashListTableGetListNext(hb)
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define SCStrdup(s)
Definition util-mem.h:56
#define likely(expr)
void CreateIsoTimeString(const SCTime_t ts, char *str, size_t size)
Definition util-time.c:209
#define SCTIME_CMP_LTE(a, b)
Definition util-time.h:106
#define SCTIME_ADD_SECS(ts, s)
Definition util-time.h:64
#define DEBUG_VALIDATE_BUG_ON(exp)
#define VARID_HASHSIZE
void VarNameStoreUnregister(const uint32_t id, const enum VarTypes type)
uint32_t VarNameStoreRegister(const char *name, const enum VarTypes type)
void VarNameStoreDestroy(void)
const char * VarNameStoreSetupLookup(const uint32_t id, const enum VarTypes type)
int VarNameStoreActivate(void)
const char * VarNameStoreLookupById(const uint32_t id, const enum VarTypes type)
find name for id+type at packet time. As the active store won't be modified, we don't need locks.
#define VARNAME_HASHSIZE
struct VarNameStore_ VarNameStore
struct VariableName_ VariableName
Name2idx mapping structure for flowbits, flowvars and pktvars.
VarNameStore * VarNameStorePtr
uint32_t VarNameStoreLookupByName(const char *name, const enum VarTypes type)
find name for id+type at packet time. As the active store won't be modified, we don't need locks.
void VarNameStoreInit(void)
VarTypes
Definition util-var.h:28