/src/suricata7/src/detect-metadata.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2007-2017 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 | | * Implements metadata keyword support |
24 | | * |
25 | | * \todo Do we need to do anything more this is used in snort host attribute table |
26 | | * It is also used for rule management. |
27 | | */ |
28 | | |
29 | | #include "suricata-common.h" |
30 | | #include "detect.h" |
31 | | #include "detect-parse.h" |
32 | | #include "detect-engine.h" |
33 | | #include "detect-metadata.h" |
34 | | #include "util-hash-string.h" |
35 | | #include "util-unittest.h" |
36 | | #include "rust.h" |
37 | | #include "util-validate.h" |
38 | | |
39 | | static int DetectMetadataSetup (DetectEngineCtx *, Signature *, const char *); |
40 | | #ifdef UNITTESTS |
41 | | static void DetectMetadataRegisterTests(void); |
42 | | #endif |
43 | | |
44 | | void DetectMetadataRegister (void) |
45 | 73 | { |
46 | 73 | sigmatch_table[DETECT_METADATA].name = "metadata"; |
47 | 73 | sigmatch_table[DETECT_METADATA].desc = "used for logging"; |
48 | 73 | sigmatch_table[DETECT_METADATA].url = "/rules/meta.html#metadata"; |
49 | 73 | sigmatch_table[DETECT_METADATA].Match = NULL; |
50 | 73 | sigmatch_table[DETECT_METADATA].Setup = DetectMetadataSetup; |
51 | 73 | sigmatch_table[DETECT_METADATA].Free = NULL; |
52 | | #ifdef UNITTESTS |
53 | | sigmatch_table[DETECT_METADATA].RegisterTests = DetectMetadataRegisterTests; |
54 | | #endif |
55 | 73 | } |
56 | | |
57 | | /** |
58 | | * \brief Free a Metadata object |
59 | | */ |
60 | | void DetectMetadataFree(DetectMetadata *mdata) |
61 | 239k | { |
62 | 239k | SCEnter(); |
63 | | |
64 | 239k | SCFree(mdata); |
65 | | |
66 | 239k | SCReturn; |
67 | 239k | } |
68 | | |
69 | | int DetectMetadataHashInit(DetectEngineCtx *de_ctx) |
70 | 135k | { |
71 | 135k | if (! DetectEngineMustParseMetadata()) |
72 | 61 | return 0; |
73 | | |
74 | 135k | de_ctx->metadata_table = HashTableInit(4096, StringHashFunc, StringHashCompareFunc, StringHashFreeFunc); |
75 | 135k | if (de_ctx->metadata_table == NULL) |
76 | 0 | return -1; |
77 | 135k | return 0; |
78 | 135k | } |
79 | | |
80 | | void DetectMetadataHashFree(DetectEngineCtx *de_ctx) |
81 | 135k | { |
82 | 135k | if (de_ctx->metadata_table) |
83 | 135k | HashTableFree(de_ctx->metadata_table); |
84 | 135k | } |
85 | | |
86 | | static const char *DetectMetadataHashAdd(DetectEngineCtx *de_ctx, const char *string) |
87 | 479k | { |
88 | 479k | const char *hstring = (char *)HashTableLookup( |
89 | 479k | de_ctx->metadata_table, (void *)string, (uint16_t)strlen(string)); |
90 | 479k | if (hstring) { |
91 | 425k | return hstring; |
92 | 425k | } |
93 | | |
94 | 53.5k | const char *astring = SCStrdup(string); |
95 | 53.5k | if (astring == NULL) { |
96 | 0 | return NULL; |
97 | 0 | } |
98 | | |
99 | 53.5k | if (HashTableAdd(de_ctx->metadata_table, (void *)astring, (uint16_t)strlen(astring)) == 0) { |
100 | 53.5k | return (char *)HashTableLookup( |
101 | 53.5k | de_ctx->metadata_table, (void *)astring, (uint16_t)strlen(astring)); |
102 | 53.5k | } else { |
103 | 0 | SCFree((void *)astring); |
104 | 0 | } |
105 | 0 | return NULL; |
106 | 53.5k | } |
107 | | |
108 | | static int SortHelper(const void *a, const void *b) |
109 | 850k | { |
110 | 850k | const DetectMetadata *ma = *(const DetectMetadata **)a; |
111 | 850k | const DetectMetadata *mb = *(const DetectMetadata **)b; |
112 | 850k | return strcasecmp(ma->key, mb->key); |
113 | 850k | } |
114 | | |
115 | | static char *CraftPreformattedJSON(const DetectMetadata *head) |
116 | 136k | { |
117 | 136k | int cnt = 0; |
118 | 573k | for (const DetectMetadata *m = head; m != NULL; m = m->next) { |
119 | 436k | cnt++; |
120 | 436k | } |
121 | 136k | if (cnt == 0) |
122 | 0 | return NULL; |
123 | | |
124 | 136k | const DetectMetadata *array[cnt]; |
125 | 136k | int i = 0; |
126 | 573k | for (const DetectMetadata *m = head; m != NULL; m = m->next) { |
127 | 436k | array[i++] = m; |
128 | 436k | } |
129 | 136k | BUG_ON(i != cnt); |
130 | 136k | qsort(array, cnt, sizeof(DetectMetadata *), SortHelper); |
131 | | |
132 | 136k | JsonBuilder *js = jb_new_object(); |
133 | 136k | if (js == NULL) |
134 | 0 | return NULL; |
135 | | |
136 | | /* array is sorted by key, so we can create a jsonbuilder object |
137 | | * with each key appearing just once with one or more values */ |
138 | 136k | bool array_open = false; |
139 | 573k | for (int j = 0; j < cnt; j++) { |
140 | 436k | const DetectMetadata *m = array[j]; |
141 | 436k | const DetectMetadata *nm = j + 1 < cnt ? array[j + 1] : NULL; |
142 | 436k | DEBUG_VALIDATE_BUG_ON(m == NULL); // for scan-build |
143 | | |
144 | 436k | if (nm && strcasecmp(m->key, nm->key) == 0) { |
145 | 114k | if (!array_open) { |
146 | 37.4k | jb_open_array(js, m->key); |
147 | 37.4k | array_open = true; |
148 | 37.4k | } |
149 | 114k | jb_append_string(js, m->value); |
150 | 322k | } else { |
151 | 322k | if (!array_open) { |
152 | 285k | jb_open_array(js, m->key); |
153 | 285k | } |
154 | 322k | jb_append_string(js, m->value); |
155 | 322k | jb_close(js); |
156 | 322k | array_open = false; |
157 | 322k | } |
158 | 436k | } |
159 | 136k | jb_close(js); |
160 | | /* we have a complete json builder. Now store it as a C string */ |
161 | 136k | const size_t len = jb_len(js); |
162 | 410k | #define MD_STR "\"metadata\":" |
163 | 273k | #define MD_STR_LEN (sizeof(MD_STR) - 1) |
164 | 136k | char *str = SCMalloc(len + MD_STR_LEN + 1); |
165 | 136k | if (str == NULL) { |
166 | 0 | jb_free(js); |
167 | 0 | return NULL; |
168 | 0 | } |
169 | 136k | char *ptr = str; |
170 | 136k | memcpy(ptr, MD_STR, MD_STR_LEN); |
171 | 136k | ptr += MD_STR_LEN; |
172 | 136k | memcpy(ptr, jb_ptr(js), len); |
173 | 136k | ptr += len; |
174 | 136k | *ptr = '\0'; |
175 | 136k | #undef MD_STR |
176 | 136k | #undef MD_STR_LEN |
177 | 136k | jb_free(js); |
178 | 136k | return str; |
179 | 136k | } |
180 | | |
181 | | static int DetectMetadataParse(DetectEngineCtx *de_ctx, Signature *s, const char *metadatastr) |
182 | 143k | { |
183 | 143k | DetectMetadata *head = s->metadata ? s->metadata->list : NULL; |
184 | 143k | char copy[strlen(metadatastr)+1]; |
185 | 143k | strlcpy(copy, metadatastr, sizeof(copy)); |
186 | 143k | char *xsaveptr = NULL; |
187 | 143k | char *key = strtok_r(copy, ",", &xsaveptr); |
188 | 452k | while (key != NULL) { |
189 | 430k | while (*key != '\0' && isblank(*key)) { |
190 | 122k | key++; |
191 | 122k | } |
192 | 308k | char *val = strchr(key, ' '); |
193 | 308k | if (val != NULL) { |
194 | 242k | *val++ = '\0'; |
195 | 379k | while (*val != '\0' && isblank(*val)) { |
196 | 137k | val++; |
197 | 137k | } |
198 | 242k | } else { |
199 | | /* Skip metadata without a value. */ |
200 | 66.0k | goto next; |
201 | 66.0k | } |
202 | | |
203 | | /* Also skip metadata if the key or value is empty. */ |
204 | 242k | if (strlen(key) == 0 || strlen(val) == 0) { |
205 | 2.53k | goto next; |
206 | 2.53k | } |
207 | | |
208 | 239k | const char *hkey = DetectMetadataHashAdd(de_ctx, key); |
209 | 239k | if (hkey == NULL) { |
210 | 0 | SCLogError("can't create metadata key"); |
211 | 0 | continue; |
212 | 0 | } |
213 | | |
214 | 239k | const char *hval = DetectMetadataHashAdd(de_ctx, val); |
215 | 239k | if (hval == NULL) { |
216 | 0 | SCLogError("can't create metadata value"); |
217 | 0 | goto next; |
218 | 0 | } |
219 | | |
220 | 239k | SCLogDebug("key: %s, value: %s", hkey, hval); |
221 | | |
222 | 239k | DetectMetadata *dkv = SCMalloc(sizeof(DetectMetadata)); |
223 | 239k | if (dkv == NULL) { |
224 | 0 | goto next; |
225 | 0 | } |
226 | 239k | dkv->key = hkey; |
227 | 239k | dkv->value = hval; |
228 | 239k | dkv->next = head; |
229 | 239k | head = dkv; |
230 | | |
231 | 308k | next: |
232 | 308k | key = strtok_r(NULL, ",", &xsaveptr); |
233 | 308k | } |
234 | 143k | if (head != NULL) { |
235 | 136k | if (s->metadata == NULL) { |
236 | 100k | s->metadata = SCCalloc(1, sizeof(*s->metadata)); |
237 | 100k | if (s->metadata == NULL) { |
238 | 0 | for (DetectMetadata *m = head; m != NULL; ) { |
239 | 0 | DetectMetadata *next_m = m->next; |
240 | 0 | DetectMetadataFree(m); |
241 | 0 | m = next_m; |
242 | 0 | } |
243 | 0 | return -1; |
244 | 0 | } |
245 | 100k | } |
246 | 136k | s->metadata->list = head; |
247 | 136k | SCFree(s->metadata->json_str); |
248 | 136k | s->metadata->json_str = CraftPreformattedJSON(head); |
249 | 136k | } |
250 | 143k | return 0; |
251 | 143k | } |
252 | | |
253 | | static int DetectMetadataSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) |
254 | 164k | { |
255 | 164k | if (DetectEngineMustParseMetadata()) { |
256 | 143k | DetectMetadataParse(de_ctx, s, rawstr); |
257 | 143k | } |
258 | | |
259 | 164k | return 0; |
260 | 164k | } |
261 | | |
262 | | #ifdef UNITTESTS |
263 | | |
264 | | static int DetectMetadataParseTest01(void) |
265 | | { |
266 | | DetectEngineUnsetParseMetadata(); |
267 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
268 | | FAIL_IF_NULL(de_ctx); |
269 | | |
270 | | Signature *sig = DetectEngineAppendSig(de_ctx, |
271 | | "alert tcp any any -> any any " |
272 | | "(metadata: toto 1; sid:1; rev:1;)"); |
273 | | FAIL_IF_NULL(sig); |
274 | | FAIL_IF(sig->metadata); |
275 | | |
276 | | DetectEngineCtxFree(de_ctx); |
277 | | PASS; |
278 | | } |
279 | | |
280 | | static int DetectMetadataParseTest02(void) |
281 | | { |
282 | | DetectEngineSetParseMetadata(); |
283 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
284 | | FAIL_IF_NULL(de_ctx); |
285 | | Signature *sig = DetectEngineAppendSig(de_ctx, |
286 | | "alert tcp any any -> any any " |
287 | | "(metadata: toto 1; " |
288 | | "metadata: titi 2, jaivu gros_minet;" |
289 | | "sid:1; rev:1;)"); |
290 | | FAIL_IF_NULL(sig); |
291 | | FAIL_IF_NULL(sig->metadata); |
292 | | FAIL_IF_NULL(sig->metadata->list); |
293 | | FAIL_IF_NULL(sig->metadata->list->key); |
294 | | FAIL_IF(strcmp("jaivu", sig->metadata->list->key)); |
295 | | FAIL_IF(strcmp("gros_minet", sig->metadata->list->value)); |
296 | | FAIL_IF_NULL(sig->metadata->list->next); |
297 | | DetectMetadata *dm = sig->metadata->list->next; |
298 | | FAIL_IF(strcmp("titi", dm->key)); |
299 | | dm = dm->next; |
300 | | FAIL_IF_NULL(dm); |
301 | | FAIL_IF(strcmp("toto", dm->key)); |
302 | | FAIL_IF_NOT(strcmp(sig->metadata->json_str, |
303 | | "\"metadata\":{\"jaivu\":[\"gros_minet\"],\"titi\":[\"2\"],\"toto\":[\"1\"]}") == 0); |
304 | | DetectEngineCtxFree(de_ctx); |
305 | | PASS; |
306 | | } |
307 | | |
308 | | /** |
309 | | * \brief this function registers unit tests for DetectCipService |
310 | | */ |
311 | | static void DetectMetadataRegisterTests(void) |
312 | | { |
313 | | UtRegisterTest("DetectMetadataParseTest01", DetectMetadataParseTest01); |
314 | | UtRegisterTest("DetectMetadataParseTest02", DetectMetadataParseTest02); |
315 | | } |
316 | | #endif /* UNITTESTS */ |