Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/libhtp/htp/htp_response_generic.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 "htp_config_auto.h"
40
41
#include "htp_private.h"
42
43
/**
44
 * Generic response line parser.
45
 * 
46
 * @param[in] connp
47
 * @return HTP status
48
 */
49
47.1k
htp_status_t htp_parse_response_line_generic(htp_connp_t *connp) {
50
47.1k
    htp_tx_t *tx = connp->out_tx;
51
47.1k
    unsigned char *data = bstr_ptr(tx->response_line);
52
47.1k
    size_t len = bstr_len(tx->response_line);
53
47.1k
    size_t pos = 0;
54
55
47.1k
    tx->response_protocol = NULL;
56
47.1k
    tx->response_protocol_number = HTP_PROTOCOL_INVALID;
57
47.1k
    tx->response_status = NULL;
58
47.1k
    tx->response_status_number = HTP_STATUS_INVALID;
59
47.1k
    tx->response_message = NULL;
60
61
    // Ignore whitespace at the beginning of the line.
62
107k
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
63
64
47.1k
    size_t start = pos;
65
66
    // Find the end of the protocol string.
67
1.24M
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
68
47.1k
    if (pos - start == 0) return HTP_OK;
69
    
70
47.1k
    tx->response_protocol = bstr_dup_mem(data + start, pos - start);
71
47.1k
    if (tx->response_protocol == NULL) return HTP_ERROR;    
72
73
47.1k
    tx->response_protocol_number = htp_parse_protocol(tx->response_protocol);
74
75
    #ifdef HTP_DEBUG
76
    fprint_raw_data(stderr, "Response protocol", bstr_ptr(tx->response_protocol), bstr_len(tx->response_protocol));
77
    fprintf(stderr, "Response protocol number: %d\n", tx->response_protocol_number);
78
    #endif
79
80
    // Ignore whitespace after the response protocol.
81
247k
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
82
47.1k
    if (pos == len) return HTP_OK;
83
84
37.7k
    start = pos;
85
86
    // Find the next whitespace character.
87
476k
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
88
37.7k
    if (pos - start == 0) return HTP_OK;
89
    
90
37.7k
    tx->response_status = bstr_dup_mem(data + start, pos - start);
91
37.7k
    if (tx->response_status == NULL) return HTP_ERROR;
92
93
37.7k
    tx->response_status_number = htp_parse_status(tx->response_status);
94
95
    #ifdef HTP_DEBUG
96
    fprint_raw_data(stderr, "Response status (as text)", bstr_ptr(tx->response_status), bstr_len(tx->response_status));
97
    fprintf(stderr, "Response status number: %d\n", tx->response_status_number);
98
    #endif
99
100
    // Ignore whitespace that follows the status code.
101
85.3k
    while ((pos < len) && (isspace(data[pos]))) pos++;
102
37.7k
    if (pos == len) return HTP_OK;
103
104
    // Assume the message stretches until the end of the line.    
105
20.6k
    tx->response_message = bstr_dup_mem(data + pos, len - pos);
106
20.6k
    if (tx->response_message == NULL) return HTP_ERROR;    
107
108
    #ifdef HTP_DEBUG
109
    fprint_raw_data(stderr, "Response status message", bstr_ptr(tx->response_message), bstr_len(tx->response_message));
110
    #endif
111
112
20.6k
    return HTP_OK;
113
20.6k
}
114
115
/**
116
 * Generic response header parser.
117
 * 
118
 * @param[in] connp
119
 * @param[in] h
120
 * @param[in] data
121
 * @param[in] len
122
 * @return HTP status
123
 */
124
299k
htp_status_t htp_parse_response_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) {
125
299k
    size_t name_start, name_end;
126
299k
    size_t value_start, value_end;
127
299k
    size_t prev;
128
129
299k
    htp_chomp(data, &len);
130
131
299k
    name_start = 0;
132
133
    // Look for the first colon.
134
299k
    size_t colon_pos = 0;
135
12.6M
    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;
136
137
299k
    if (colon_pos == len) {
138
        // Header line with a missing colon.
139
140
127k
        h->flags |= HTP_FIELD_UNPARSEABLE;
141
127k
        h->flags |= HTP_FIELD_INVALID;
142
143
127k
        if (!(connp->out_tx->flags & HTP_FIELD_UNPARSEABLE)) {
144
            // Only once per transaction.
145
19.8k
            connp->out_tx->flags |= HTP_FIELD_UNPARSEABLE;
146
19.8k
            connp->out_tx->flags |= HTP_FIELD_INVALID;
147
19.8k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: missing colon.");
148
19.8k
        }
149
       
150
        // Reset the position. We're going to treat this invalid header
151
        // as a header with an empty name. That will increase the probability
152
        // that the content will be inspected.
153
127k
        colon_pos = 0;
154
127k
        (void)colon_pos; // suppress scan-build warning
155
127k
        name_end = 0;
156
127k
        value_start = 0;
157
172k
    } else {
158
        // Header line with a colon.
159
        
160
172k
        if (colon_pos == 0) {
161
            // Empty header name.
162
163
7.43k
            h->flags |= HTP_FIELD_INVALID;
164
165
7.43k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
166
                // Only once per transaction.
167
3.63k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
168
3.63k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: empty name.");
169
3.63k
            }
170
7.43k
        }
171
172
172k
        name_end = colon_pos;
173
174
        // Ignore unprintable after field-name.
175
172k
        prev = name_end;
176
189k
        while ((prev > name_start) && htp_is_space(data[prev - 1])) {
177
16.7k
            prev--;
178
16.7k
            name_end--;
179
180
16.7k
            h->flags |= HTP_FIELD_INVALID;
181
182
16.7k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
183
                // Only once per transaction.
184
4.34k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
185
4.34k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: LWS after name.");
186
4.34k
            }
187
16.7k
        }
188
189
172k
        value_start = colon_pos + 1;
190
172k
    }
191
192
    // Header value.   
193
194
    // Ignore LWS before field-content.
195
411k
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
196
111k
        value_start++;
197
111k
    }
198
199
    // Look for the end of field-content.
200
299k
    value_end = len;    
201
    
202
    // Check that the header name is a token.
203
299k
    size_t i = name_start;
204
1.97M
    while (i < name_end) {
205
1.73M
        if (!htp_is_token(data[i])) {
206
58.2k
            h->flags |= HTP_FIELD_INVALID;
207
208
58.2k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
209
9.26k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
210
9.26k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header name is not a token.");
211
9.26k
            }
212
213
58.2k
            break;
214
58.2k
        }
215
216
1.67M
        i++;
217
1.67M
    }
218
5.09M
    for (i = value_start; i < value_end; i++) {
219
4.88M
        if (data[i] == 0) {
220
88.3k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header value contains null.");
221
88.3k
            break;
222
88.3k
        }
223
4.88M
    }
224
    // Ignore LWS after field-content.
225
299k
    prev = value_end - 1;
226
314k
    while ((prev > value_start) && (htp_is_lws(data[prev]))) {
227
14.6k
        prev--;
228
14.6k
        value_end--;
229
14.6k
    }
230
231
    // Now extract the name and the value.
232
299k
    h->name = bstr_dup_mem(data + name_start, name_end - name_start);
233
299k
    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
234
299k
    if ((h->name == NULL) || (h->value == NULL)) {
235
0
        bstr_free(h->name);
236
0
        bstr_free(h->value);
237
0
        return HTP_ERROR;
238
0
    }
239
240
299k
    return HTP_OK;
241
299k
}
242
243
/**
244
 * Generic response header line(s) processor, which assembles folded lines
245
 * into a single buffer before invoking the parsing function.
246
 * 
247
 * @param[in] connp
248
 * @param[in] data
249
 * @param[in] len
250
 * @return HTP status
251
 */
252
299k
htp_status_t htp_process_response_header_generic(htp_connp_t *connp, unsigned char *data, size_t len) {
253
    // Create a new header structure.
254
299k
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
255
299k
    if (h == NULL) return HTP_ERROR;
256
257
299k
    if (htp_parse_response_header_generic(connp, h, data, len) != HTP_OK) {
258
0
        free(h);
259
0
        return HTP_ERROR;
260
0
    }
261
262
    #ifdef HTP_DEBUG
263
    fprint_bstr(stderr, "Header name", h->name);
264
    fprint_bstr(stderr, "Header value", h->value);
265
    #endif
266
267
    // Do we already have a header with the same name?
268
299k
    htp_header_t *h_existing = htp_table_get(connp->out_tx->response_headers, h->name);
269
299k
    if (h_existing != NULL) {
270
        // Keep track of repeated same-name headers.
271
160k
        if ((h_existing->flags & HTP_FIELD_REPEATED) == 0) {
272
            // This is the second occurence for this header.
273
33.3k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Repetition for header");
274
127k
        } else {
275
            // For simplicity reasons, we count the repetitions of all headers
276
127k
            if (connp->out_tx->res_header_repetitions < HTP_MAX_HEADERS_REPETITIONS) {
277
86.6k
                connp->out_tx->res_header_repetitions++;
278
86.6k
            } else {
279
40.6k
                bstr_free(h->name);
280
40.6k
                bstr_free(h->value);
281
40.6k
                free(h);
282
40.6k
                return HTP_OK;
283
40.6k
            }
284
127k
        }
285
120k
        h_existing->flags |= HTP_FIELD_REPEATED;
286
287
        // Having multiple C-L headers is against the RFC but many
288
        // browsers ignore the subsequent headers if the values are the same.
289
120k
        if (bstr_cmp_c_nocase(h->name, "Content-Length") == 0) {
290
            // Don't use string comparison here because we want to
291
            // ignore small formatting differences.
292
293
20.6k
            int64_t existing_cl, new_cl;
294
295
20.6k
            existing_cl = htp_parse_content_length(h_existing->value, NULL);
296
20.6k
            new_cl = htp_parse_content_length(h->value, NULL);
297
20.6k
            if ((existing_cl == -1) || (new_cl == -1) || (existing_cl != new_cl)) {
298
                // Ambiguous response C-L value.
299
11.3k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Ambiguous response C-L value");
300
11.3k
            }
301
302
            // Ignoring the new C-L header that has the same value as the previous ones.
303
99.4k
        } else {            
304
            // Add to the existing header.
305
306
99.4k
            bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value) + 2 + bstr_len(h->value));
307
99.4k
            if (new_value == NULL) {
308
0
                bstr_free(h->name);
309
0
                bstr_free(h->value);
310
0
                free(h);
311
0
                return HTP_ERROR;
312
0
            }
313
314
99.4k
            h_existing->value = new_value;
315
99.4k
            bstr_add_mem_noex(h_existing->value, (unsigned char *) ", ", 2);
316
99.4k
            bstr_add_noex(h_existing->value, h->value);
317
99.4k
        }
318
319
        // The new header structure is no longer needed.
320
120k
        bstr_free(h->name);
321
120k
        bstr_free(h->value);
322
120k
        free(h);       
323
138k
    } else {
324
138k
        if (htp_table_size(connp->out_tx->response_headers) > connp->cfg->number_headers_limit) {
325
0
            if (!(connp->out_tx->flags & HTP_HEADERS_TOO_MANY)) {
326
0
                connp->out_tx->flags |= HTP_HEADERS_TOO_MANY;
327
0
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Too many response headers");
328
0
            }
329
0
            bstr_free(h->name);
330
0
            bstr_free(h->value);
331
0
            free(h);
332
0
            return HTP_ERROR;
333
0
        }
334
        // Add as a new header.
335
138k
        if (htp_table_add(connp->out_tx->response_headers, h->name, h) != HTP_OK) {
336
0
            bstr_free(h->name);
337
0
            bstr_free(h->value);
338
0
            free(h);
339
0
            return HTP_ERROR;
340
0
        }
341
138k
    }
342
   
343
258k
    return HTP_OK;
344
299k
}