Coverage Report

Created: 2025-07-12 06:24

/src/libhtp/test/test.c
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
}