Line | Count | Source (jump to first uncovered line) |
1 | | /*************************************************************************** |
2 | | * Copyright (c) 2009-2010 Open Information Security Foundation |
3 | | * Copyright (c) 2010-2013 Qualys, Inc. |
4 | | * All rights reserved. |
5 | | * |
6 | | * Redistribution and use in source and binary forms, with or without |
7 | | * modification, are permitted provided that the following conditions are |
8 | | * met: |
9 | | * |
10 | | * - Redistributions of source code must retain the above copyright |
11 | | * notice, this list of conditions and the following disclaimer. |
12 | | |
13 | | * - Redistributions in binary form must reproduce the above copyright |
14 | | * notice, this list of conditions and the following disclaimer in the |
15 | | * documentation and/or other materials provided with the distribution. |
16 | | |
17 | | * - Neither the name of the Qualys, Inc. nor the names of its |
18 | | * contributors may be used to endorse or promote products derived from |
19 | | * this software without specific prior written permission. |
20 | | * |
21 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
22 | | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
23 | | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
24 | | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
25 | | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
26 | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
27 | | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
28 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
29 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
30 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
31 | | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
32 | | ***************************************************************************/ |
33 | | |
34 | | /** |
35 | | * @file |
36 | | * @author Ivan Ristic <ivanr@webkreator.com> |
37 | | */ |
38 | | |
39 | | #include <assert.h> |
40 | | #include <fcntl.h> |
41 | | #include <unistd.h> |
42 | | #include <stdio.h> |
43 | | #include <stdlib.h> |
44 | | #include <sys/stat.h> |
45 | | #include <sys/time.h> |
46 | | #include <sys/types.h> |
47 | | |
48 | | #include "../htp/htp.h" |
49 | | #include "test.h" |
50 | | |
51 | | /** |
52 | | * Destroys a test. |
53 | | * |
54 | | * @param[in] test |
55 | | */ |
56 | 0 | static void test_destroy(test_t *test) { |
57 | 0 | if (test->buf != NULL) { |
58 | 0 | free(test->buf); |
59 | 0 | test->buf = NULL; |
60 | 0 | } |
61 | 0 | } |
62 | | |
63 | | /** |
64 | | * Checks if there's a chunk boundary at the given position. |
65 | | * |
66 | | * @param[in] test |
67 | | * @param[in] pos |
68 | | * @return Zero if there is no boundary, SERVER or CLIENT if a boundary |
69 | | * was found, and a negative value on error (e.g., not enough data |
70 | | * to determine if a boundary is present). |
71 | | */ |
72 | 4.05M | static int test_is_boundary(test_t *test, size_t pos) { |
73 | | // Check that there's enough room |
74 | 4.05M | if (pos + 3 >= test->len) return -1; |
75 | | |
76 | 4.04M | if ((test->buf[pos] == '<') && (test->buf[pos + 1] == '<' || test->buf[pos + 1] == '>') && (test->buf[pos + 2] == '<')) { |
77 | 228k | if (test->buf[pos + 3] == '\n') { |
78 | 148k | return SERVER; |
79 | 148k | } |
80 | | |
81 | 79.6k | if (test->buf[pos + 3] == '\r') { |
82 | 67.1k | if (pos + 4 >= test->len) return -1; |
83 | 67.1k | else if (test->buf[pos + 4] == '\n') { |
84 | 63.9k | return SERVER; |
85 | 63.9k | } |
86 | 67.1k | } |
87 | 79.6k | } |
88 | | |
89 | 3.83M | if ((test->buf[pos] == '>') && (test->buf[pos + 1] == '>' || test->buf[pos + 1] == '<') && (test->buf[pos + 2] == '>')) { |
90 | 372k | if (test->buf[pos + 3] == '\n') { |
91 | 210k | return CLIENT; |
92 | 210k | } |
93 | | |
94 | 162k | if (test->buf[pos + 3] == '\r') { |
95 | 150k | if (pos + 4 >= test->len) return -1; |
96 | 150k | else if (test->buf[pos + 4] == '\n') { |
97 | 145k | return CLIENT; |
98 | 145k | } |
99 | 150k | } |
100 | 162k | } |
101 | | |
102 | 3.47M | return 0; |
103 | 3.83M | } |
104 | | |
105 | | /** |
106 | | * Initializes test by loading the entire data file into a memory block. |
107 | | * |
108 | | * @param[in] test |
109 | | * @param[in] filename |
110 | | * @return Non-negative value on success, negative value on error. |
111 | | */ |
112 | 0 | static int test_init(test_t *test, const char *filename, int clone_count) { |
113 | 0 | memset(test, 0, sizeof (test_t)); |
114 | |
|
115 | 0 | int fd = open(filename, O_RDONLY | O_BINARY); |
116 | 0 | if (fd < 0) return -1; |
117 | | |
118 | 0 | struct stat buf; |
119 | 0 | if (fstat(fd, &buf) < 0) { |
120 | 0 | close(fd); |
121 | 0 | return -1; |
122 | 0 | } |
123 | | |
124 | 0 | test->buf = malloc(buf.st_size * clone_count + clone_count - 1); |
125 | 0 | test->len = 0; |
126 | 0 | test->pos = 0; |
127 | | |
128 | | // Check that we received our memory. |
129 | 0 | assert(test->buf != NULL); |
130 | | |
131 | 0 | int bytes_read = 0; |
132 | 0 | while ((bytes_read = read(fd, test->buf + test->len, buf.st_size - test->len)) > 0) { |
133 | 0 | test->len += bytes_read; |
134 | 0 | } |
135 | |
|
136 | 0 | if ((int)test->len != buf.st_size) { |
137 | 0 | free(test->buf); |
138 | 0 | close(fd); |
139 | 0 | return -2; |
140 | 0 | } |
141 | | |
142 | 0 | close(fd); |
143 | |
|
144 | 0 | int i = 1; |
145 | 0 | for (i = 1; i < clone_count; i++) { |
146 | 0 | test->buf[i * buf.st_size + (i-1)] = '\n'; |
147 | 0 | memcpy(test->buf + i * buf.st_size + i, test->buf, buf.st_size); |
148 | 0 | } |
149 | | |
150 | 0 | test->len = buf.st_size * clone_count + clone_count - 1; |
151 | |
|
152 | 0 | return 1; |
153 | 0 | } |
154 | | |
155 | 0 | static void test_start(test_t *test) { |
156 | 0 | test->pos = 0; |
157 | 0 | } |
158 | | |
159 | | /** |
160 | | * Finds the next data chunk in the given test. |
161 | | * |
162 | | * @param[in] test |
163 | | * @return One if a chunk is found or zero if there are no more chunks in the test. On |
164 | | * success, test->chunk will point to the beginning of the chunk, while |
165 | | * test->chunk_len will contain its length. |
166 | | */ |
167 | 226k | int test_next_chunk(test_t *test) { |
168 | 226k | if (test->pos >= test->len) { |
169 | 9.61k | return 0; |
170 | 9.61k | } |
171 | | |
172 | 216k | test->chunk = NULL; |
173 | 216k | int isgap = 0; |
174 | | |
175 | 101M | while (test->pos < test->len) { |
176 | | // Do we need to start another chunk? |
177 | 101M | if (test->chunk == NULL) { |
178 | | // Are we at a boundary |
179 | 289k | test->chunk_direction = test_is_boundary(test, test->pos); |
180 | 289k | if (test->chunk_direction <= 0) { |
181 | | // Error |
182 | 118 | return -1; |
183 | 118 | } |
184 | | |
185 | 289k | if (test->buf[test->pos + 1] != test->buf[test->pos + 2]) { |
186 | 15.6k | isgap = 1; |
187 | 273k | } else { |
188 | 273k | isgap = 0; |
189 | 273k | } |
190 | | // Move over the boundary |
191 | 289k | test->pos += 4; |
192 | 289k | if (test->pos >= test->len) { |
193 | 75 | return 0; |
194 | 75 | } |
195 | 289k | if (test->buf[test->pos-1] == '\r') test->pos++; |
196 | 289k | if (test->pos >= test->len) { |
197 | 38 | return 0; |
198 | 38 | } |
199 | | |
200 | | // Start new chunk |
201 | 289k | test->chunk = test->buf + test->pos; |
202 | 289k | test->chunk_offset = test->pos; |
203 | | // if it is empty (boundary already), continue to next chunk |
204 | 289k | if (test_is_boundary(test, test->pos) > 0) { |
205 | 72.7k | test->chunk = NULL; |
206 | 72.7k | continue; |
207 | 72.7k | } |
208 | 289k | } |
209 | | |
210 | | // Are we at the end of a line? |
211 | 101M | if (test->buf[test->pos] == '\n') { |
212 | 3.47M | int r = test_is_boundary(test, test->pos + 1); |
213 | 3.47M | if ((r == CLIENT) || (r == SERVER)) { |
214 | | // We got ourselves a chunk |
215 | 206k | test->chunk_len = test->pos - test->chunk_offset; |
216 | | |
217 | | // Remove one '\r' (in addition to the '\n' that we've already removed), |
218 | | // which belongs to the next boundary |
219 | 206k | if ((test->chunk_len > 0) && (test->chunk[test->chunk_len - 1] == '\r')) { |
220 | 15.8k | test->chunk_len--; |
221 | 15.8k | } |
222 | | |
223 | | // Position at the next boundary line |
224 | 206k | test->pos++; |
225 | 206k | if (test->pos >= test->len) { |
226 | 0 | return 0; |
227 | 0 | } |
228 | 206k | if (isgap) { |
229 | 13.2k | test->chunk = NULL; |
230 | 13.2k | } |
231 | | |
232 | 206k | return 1; |
233 | 206k | } |
234 | 3.47M | } |
235 | | |
236 | 101M | test->pos++; |
237 | 101M | } |
238 | | |
239 | | |
240 | 9.67k | if (test->chunk != NULL) { |
241 | 9.67k | test->chunk_len = test->pos - test->chunk_offset; |
242 | 9.67k | if (isgap) { |
243 | 140 | test->chunk = NULL; |
244 | 140 | } |
245 | 9.67k | return 1; |
246 | 9.67k | } |
247 | | |
248 | 0 | return 0; |
249 | 9.67k | } |
250 | | |
251 | 0 | static int parse_filename(const char *filename, char **remote_addr, int *remote_port, char **local_addr, int *local_port) { |
252 | 0 | char *copy = strdup(filename); |
253 | 0 | char *p, *saveptr; |
254 | |
|
255 | 0 | char *start = copy; |
256 | 0 | char *q = strrchr(copy, '/'); |
257 | 0 | if (q != NULL) start = q; |
258 | |
|
259 | 0 | q = strrchr(start, '\\'); |
260 | 0 | if (q != NULL) start = q; |
261 | |
|
262 | 0 | int count = 0; |
263 | 0 | p = strtok_r(start, "_", &saveptr); |
264 | 0 | while (p != NULL) { |
265 | 0 | count++; |
266 | | // printf("%i %s\n", count, p); |
267 | |
|
268 | 0 | switch (count) { |
269 | 0 | case 2: |
270 | 0 | *remote_addr = strdup(p); |
271 | 0 | break; |
272 | 0 | case 3: |
273 | 0 | *remote_port = atoi(p); |
274 | 0 | break; |
275 | 0 | case 4: |
276 | 0 | *local_addr = strdup(p); |
277 | 0 | break; |
278 | 0 | case 5: |
279 | 0 | *local_port = atoi(p); |
280 | 0 | break; |
281 | 0 | } |
282 | | |
283 | 0 | p = strtok_r(NULL, "_", &saveptr); |
284 | 0 | } |
285 | | |
286 | 0 | free(copy); |
287 | |
|
288 | 0 | return 0; |
289 | 0 | } |
290 | | |
291 | | /** |
292 | | * Runs a single test. |
293 | | * |
294 | | * @param[in] filename |
295 | | * @param[in] cfg |
296 | | * @return A pointer to the instance of htp_connp_t created during |
297 | | * the test, or NULL if the test failed for some reason. |
298 | | */ |
299 | 0 | int test_run_ex(const char *testsdir, const char *testname, htp_cfg_t *cfg, htp_connp_t **connp, int clone_count) { |
300 | 0 | char filename[1025]; |
301 | 0 | test_t test; |
302 | 0 | struct timeval tv_start, tv_end; |
303 | 0 | int rc; |
304 | |
|
305 | 0 | *connp = NULL; |
306 | |
|
307 | 0 | strncpy(filename, testsdir, 1024); |
308 | 0 | strncat(filename, "/", 1024 - strlen(filename)); |
309 | 0 | strncat(filename, testname, 1024 - strlen(filename)); |
310 | | |
311 | | // printf("Filename: %s\n", filename); |
312 | | |
313 | | // Initinialize test |
314 | |
|
315 | 0 | rc = test_init(&test, filename, clone_count); |
316 | 0 | if (rc < 0) { |
317 | 0 | return rc; |
318 | 0 | } |
319 | | |
320 | 0 | gettimeofday(&tv_start, NULL); |
321 | |
|
322 | 0 | test_start(&test); |
323 | | |
324 | | // Create parser |
325 | 0 | *connp = htp_connp_create(cfg); |
326 | 0 | if (*connp == NULL) { |
327 | 0 | fprintf(stderr, "Failed to create connection parser\n"); |
328 | 0 | exit(1); |
329 | 0 | } |
330 | | |
331 | 0 | htp_connp_set_user_data(*connp, (void *) 0x02); |
332 | | |
333 | | // Does the filename contain connection metdata? |
334 | 0 | if (strncmp(testname, "stream", 6) == 0) { |
335 | | // It does; use it |
336 | 0 | char *remote_addr = NULL, *local_addr = NULL; |
337 | 0 | int remote_port = -1, local_port = -1; |
338 | |
|
339 | 0 | parse_filename(testname, &remote_addr, &remote_port, &local_addr, &local_port); |
340 | 0 | htp_connp_open(*connp, (const char *) remote_addr, remote_port, (const char *) local_addr, local_port, &tv_start); |
341 | 0 | free(remote_addr); |
342 | 0 | free(local_addr); |
343 | 0 | } else { |
344 | | // No connection metadata; provide some fake information instead |
345 | 0 | htp_connp_open(*connp, (const char *) "127.0.0.1", 10000, (const char *) "127.0.0.1", 80, &tv_start); |
346 | 0 | } |
347 | | |
348 | | // Find all chunks and feed them to the parser |
349 | 0 | int in_data_other = 0; |
350 | 0 | char *in_data = NULL; |
351 | 0 | size_t in_data_len = 0; |
352 | 0 | size_t in_data_offset = 0; |
353 | |
|
354 | 0 | int out_data_other = 0; |
355 | 0 | char *out_data = NULL; |
356 | 0 | size_t out_data_len = 0; |
357 | 0 | size_t out_data_offset = 0; |
358 | |
|
359 | 0 | for (;;) { |
360 | 0 | if (test_next_chunk(&test) <= 0) { |
361 | 0 | break; |
362 | 0 | } |
363 | | |
364 | 0 | if (test.chunk_direction == CLIENT) { |
365 | 0 | if (in_data_other) { |
366 | 0 | test_destroy(&test); |
367 | 0 | fprintf(stderr, "Unable to buffer more than one inbound chunk.\n"); |
368 | 0 | return -1; |
369 | 0 | } |
370 | | |
371 | 0 | rc = htp_connp_req_data(*connp, &tv_start, test.chunk, test.chunk_len); |
372 | 0 | if (rc == HTP_STREAM_ERROR) { |
373 | 0 | test_destroy(&test); |
374 | 0 | return -101; |
375 | 0 | } |
376 | 0 | if (rc == HTP_STREAM_DATA_OTHER) { |
377 | | // Parser needs to see the outbound stream in order to continue |
378 | | // parsing the inbound stream. |
379 | 0 | in_data_other = 1; |
380 | 0 | in_data = test.chunk; |
381 | 0 | in_data_len = test.chunk_len; |
382 | 0 | in_data_offset = htp_connp_req_data_consumed(*connp); |
383 | 0 | } |
384 | 0 | } else { |
385 | 0 | if (out_data_other) { |
386 | 0 | rc = htp_connp_res_data(*connp, &tv_start, out_data + out_data_offset, out_data_len - out_data_offset); |
387 | 0 | if (rc == HTP_STREAM_ERROR) { |
388 | 0 | test_destroy(&test); |
389 | 0 | return -104; |
390 | 0 | } |
391 | | |
392 | 0 | out_data_other = 0; |
393 | 0 | } |
394 | | |
395 | 0 | rc = htp_connp_res_data(*connp, &tv_start, test.chunk, test.chunk_len); |
396 | 0 | if (rc == HTP_STREAM_ERROR) { |
397 | 0 | test_destroy(&test); |
398 | 0 | return -102; |
399 | 0 | } |
400 | 0 | if (rc == HTP_STREAM_DATA_OTHER) { |
401 | | // Parser needs to see the outbound stream in order to continue |
402 | | // parsing the inbound stream. |
403 | 0 | out_data_other = 1; |
404 | 0 | out_data = test.chunk; |
405 | 0 | out_data_len = test.chunk_len; |
406 | 0 | out_data_offset = htp_connp_res_data_consumed(*connp); |
407 | | // printf("# YYY out offset is %d\n", out_data_offset); |
408 | 0 | } |
409 | |
|
410 | 0 | if (in_data_other) { |
411 | 0 | rc = htp_connp_req_data(*connp, &tv_start, in_data + in_data_offset, in_data_len - in_data_offset); |
412 | 0 | if (rc == HTP_STREAM_ERROR) { |
413 | 0 | test_destroy(&test); |
414 | 0 | return -103; |
415 | 0 | } |
416 | | |
417 | 0 | in_data_other = 0; |
418 | 0 | } |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | 0 | if (out_data_other) { |
423 | 0 | rc = htp_connp_res_data(*connp, &tv_start, out_data + out_data_offset, out_data_len - out_data_offset); |
424 | 0 | if (rc == HTP_STREAM_ERROR) { |
425 | 0 | test_destroy(&test); |
426 | 0 | return -104; |
427 | 0 | } |
428 | 0 | out_data_other = 0; |
429 | 0 | } |
430 | | |
431 | 0 | gettimeofday(&tv_end, NULL); |
432 | | |
433 | | // Close the connection |
434 | 0 | htp_connp_close(*connp, &tv_end); |
435 | | |
436 | | // Clean up |
437 | 0 | test_destroy(&test); |
438 | |
|
439 | 0 | return 1; |
440 | 0 | } |
441 | | |
442 | 0 | int test_run(const char *testsdir, const char *testname, htp_cfg_t *cfg, htp_connp_t **connp) { |
443 | 0 | return test_run_ex(testsdir, testname, cfg, connp, 1); |
444 | 0 | } |