Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/libhtp/htp/htp_decompressors.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
0
static void *SzAlloc(ISzAllocPtr p, size_t size) { return malloc(size); }
45
0
static void SzFree(ISzAllocPtr p, void *address) { free(address); }
46
const ISzAlloc lzma_Alloc = { SzAlloc, SzFree };
47
48
49
/**
50
 *  @brief See if the header has extensions
51
 *  @return number of bytes to skip
52
 */
53
static size_t htp_gzip_decompressor_probe(const unsigned char *data,
54
                                       size_t data_len)
55
26.0k
{
56
26.0k
    if (data_len < 4)
57
1.98k
        return 0;
58
59
24.0k
    size_t consumed = 0;
60
61
24.0k
    if (data[0] == 0x1f && data[1] == 0x8b && data[3] != 0) {
62
7.64k
        if (data[3] & (1 << 3) || data[3] & (1 << 4)) {
63
            /* skip past
64
             * - FNAME extension, which is a name ended in a NUL terminator
65
             * or
66
             * - FCOMMENT extension, which is a commend ended in a NULL terminator
67
             */
68
69
4.44k
            size_t len;
70
146k
            for (len = 10; len < data_len && data[len] != '\0'; len++);
71
4.44k
            consumed = len + 1;
72
73
            //printf("skipped %u bytes for FNAME/FCOMMENT header (GZIP)\n", (uint)consumed);
74
75
4.44k
        } else if (data[3] & (1 << 1)) {
76
2.17k
            consumed = 12;
77
            //printf("skipped %u bytes for FHCRC header (GZIP)\n", 12);
78
79
2.17k
        } else {
80
            //printf("GZIP unknown/unsupported flags %02X\n", data[3]);
81
1.02k
            consumed = 10;
82
1.02k
        }
83
7.64k
    }
84
85
24.0k
    if (consumed > data_len)
86
2.64k
        return 0;
87
88
21.4k
    return consumed;
89
24.0k
}
90
91
/**
92
 *  @brief restart the decompressor
93
 *  @return 1 if it restarted, 0 otherwise
94
 */
95
static int htp_gzip_decompressor_restart(htp_decompressor_gzip_t *drec,
96
                                         const unsigned char *data,
97
                                         size_t data_len, size_t *consumed_back)
98
33.1k
{
99
33.1k
    size_t consumed = 0;
100
33.1k
    int rc = 0;
101
102
33.1k
    if (drec->restart < 3) {
103
104
        // first retry with the existing type, but now consider the
105
        // extensions
106
26.0k
        if (drec->restart == 0) {
107
9.33k
            consumed = htp_gzip_decompressor_probe(data, data_len);
108
109
9.33k
            if (drec->zlib_initialized == HTP_COMPRESSION_GZIP) {
110
                //printf("GZIP restart, consumed %u\n", (uint)consumed);
111
8.35k
                rc = inflateInit2(&drec->stream, 15 + 32);
112
8.35k
            } else {
113
                //printf("DEFLATE restart, consumed %u\n", (uint)consumed);
114
979
                rc = inflateInit2(&drec->stream, -15);
115
979
            }
116
9.33k
            if (rc != Z_OK)
117
0
                return 0;
118
119
9.33k
            goto restart;
120
121
        // if that still fails, try the other method we support
122
123
16.7k
        } else if (drec->zlib_initialized == HTP_COMPRESSION_DEFLATE) {
124
7.41k
            rc = inflateInit2(&drec->stream, 15 + 32);
125
7.41k
            if (rc != Z_OK)
126
0
                return 0;
127
128
7.41k
            drec->zlib_initialized = HTP_COMPRESSION_GZIP;
129
7.41k
            consumed = htp_gzip_decompressor_probe(data, data_len);
130
#if 0
131
            printf("DEFLATE -> GZIP consumed %u\n", (uint)consumed);
132
#endif
133
7.41k
            goto restart;
134
135
9.31k
        } else if (drec->zlib_initialized == HTP_COMPRESSION_GZIP) {
136
9.31k
            rc = inflateInit2(&drec->stream, -15);
137
9.31k
            if (rc != Z_OK)
138
0
                return 0;
139
140
9.31k
            drec->zlib_initialized = HTP_COMPRESSION_DEFLATE;
141
9.31k
            consumed = htp_gzip_decompressor_probe(data, data_len);
142
#if 0
143
            printf("GZIP -> DEFLATE consumed %u\n", (uint)consumed);
144
#endif
145
9.31k
            goto restart;
146
9.31k
        }
147
26.0k
    }
148
7.09k
    return 0;
149
150
26.0k
restart:
151
#if 0
152
    gz_header y;
153
    gz_headerp x = &y;
154
    int res = inflateGetHeader(&drec->stream, x);
155
    printf("HEADER res %d x.os %d x.done %d\n", res, x->os, x->done);
156
#endif
157
26.0k
    *consumed_back = consumed;
158
26.0k
    drec->restart++;
159
26.0k
    return 1;
160
33.1k
}
161
162
/**
163
 * Ends decompressor.
164
 *
165
 * @param[in] drec
166
 */
167
21.4k
static void htp_gzip_decompressor_end(htp_decompressor_gzip_t *drec) {
168
21.4k
    if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
169
0
        LzmaDec_Free(&drec->state, &lzma_Alloc);
170
0
        drec->zlib_initialized = 0;
171
21.4k
    } else if (drec->zlib_initialized) {
172
11.7k
        inflateEnd(&drec->stream);
173
11.7k
        drec->zlib_initialized = 0;
174
11.7k
    }
175
21.4k
}
176
177
/**
178
 * Decompress a chunk of gzip-compressed data.
179
 * If we have more than one decompressor, call this function recursively.
180
 *
181
 * @param[in] drec
182
 * @param[in] d
183
 * @return HTP_OK on success, HTP_ERROR or some other negative integer on failure.
184
 */
185
546k
htp_status_t htp_gzip_decompressor_decompress(htp_decompressor_t *drec1, htp_tx_data_t *d) {
186
546k
    size_t consumed = 0;
187
546k
    int rc = 0;
188
546k
    htp_status_t callback_rc;
189
546k
    htp_decompressor_gzip_t *drec = (htp_decompressor_gzip_t*) drec1;
190
191
    // Pass-through the NULL chunk, which indicates the end of the stream.
192
193
546k
    if (drec->super.passthrough) {
194
339k
        htp_tx_data_t d2;
195
339k
        d2.tx = d->tx;
196
339k
        d2.data = d->data;
197
339k
        d2.len = d->len;
198
339k
        d2.is_last = d->is_last;
199
200
339k
        callback_rc = drec->super.callback(&d2);
201
339k
        if (callback_rc != HTP_OK) {
202
67
            return HTP_ERROR;
203
67
        }
204
205
339k
        return HTP_OK;
206
339k
    } else if (drec->zlib_initialized == HTP_COMPRESSION_OVER) {
207
122k
        return HTP_ERROR;
208
122k
    }
209
210
85.1k
    if (d->data == NULL) {
211
        // Prepare data for callback.
212
2.96k
        htp_tx_data_t dout;
213
2.96k
        dout.tx = d->tx;
214
        // This is last call, so output uncompressed data so far
215
2.96k
        dout.len = GZIP_BUF_SIZE - drec->stream.avail_out;
216
2.96k
        if (dout.len > 0) {
217
690
            dout.data = drec->buffer;
218
2.27k
        } else {
219
2.27k
            dout.data = NULL;
220
2.27k
        }
221
2.96k
        dout.is_last = d->is_last;
222
2.96k
        if (drec->super.next != NULL && drec->zlib_initialized) {
223
613
            return htp_gzip_decompressor_decompress(drec->super.next, &dout);
224
2.35k
        } else {
225
            // Send decompressed data to the callback.
226
2.35k
            callback_rc = drec->super.callback(&dout);
227
2.35k
            if (callback_rc != HTP_OK) {
228
0
                htp_gzip_decompressor_end(drec);
229
0
                return callback_rc;
230
0
            }
231
2.35k
        }
232
233
2.35k
        return HTP_OK;
234
2.96k
    }
235
236
108k
restart:
237
108k
    if (consumed > d->len || d->len > UINT32_MAX ) {
238
0
        htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "GZip decompressor: consumed > d->len");
239
0
        return HTP_ERROR;
240
0
    }
241
108k
    drec->stream.next_in = (unsigned char *) (d->data + consumed);
242
108k
    drec->stream.avail_in = (uint32_t) (d->len - consumed);
243
244
408k
    while (drec->stream.avail_in != 0) {
245
        // If there's no more data left in the
246
        // buffer, send that information out.
247
365k
        if (drec->stream.avail_out == 0) {
248
259k
            drec->crc = crc32(drec->crc, drec->buffer, GZIP_BUF_SIZE);
249
250
            // Prepare data for callback.
251
259k
            htp_tx_data_t d2;
252
259k
            d2.tx = d->tx;
253
259k
            d2.data = drec->buffer;
254
259k
            d2.len = GZIP_BUF_SIZE;
255
259k
            d2.is_last = d->is_last;
256
257
259k
            if (drec->super.next != NULL && drec->zlib_initialized) {
258
5.99k
                callback_rc = htp_gzip_decompressor_decompress(drec->super.next, &d2);
259
253k
            } else {
260
                // Send decompressed data to callback.
261
253k
                callback_rc = drec->super.callback(&d2);
262
253k
            }
263
259k
            if (callback_rc != HTP_OK) {
264
1.39k
                htp_gzip_decompressor_end(drec);
265
1.39k
                return callback_rc;
266
1.39k
            }
267
268
257k
            drec->stream.next_out = drec->buffer;
269
257k
            drec->stream.avail_out = GZIP_BUF_SIZE;
270
257k
        }
271
272
363k
        if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
273
0
            if (drec->header_len < LZMA_PROPS_SIZE + 8) {
274
0
                consumed = LZMA_PROPS_SIZE + 8 - drec->header_len;
275
0
                if (consumed > drec->stream.avail_in) {
276
0
                    consumed = drec->stream.avail_in;
277
0
                }
278
0
                memcpy(drec->header + drec->header_len, drec->stream.next_in, consumed);
279
0
                drec->stream.next_in = (unsigned char *) (d->data + consumed);
280
0
                drec->stream.avail_in = (uint32_t) (d->len - consumed);
281
0
                drec->header_len += consumed;
282
0
            }
283
0
            if (drec->header_len == LZMA_PROPS_SIZE + 8) {
284
0
                rc = LzmaDec_Allocate(&drec->state, drec->header, LZMA_PROPS_SIZE, &lzma_Alloc);
285
0
                if (rc != SZ_OK)
286
0
                    return rc;
287
0
                LzmaDec_Init(&drec->state);
288
                // hacky to get to next step end retry allocate in case of failure
289
0
                drec->header_len++;
290
0
            }
291
0
            if (drec->header_len > LZMA_PROPS_SIZE + 8) {
292
0
                size_t inprocessed = drec->stream.avail_in;
293
0
                size_t outprocessed = drec->stream.avail_out;
294
0
                ELzmaStatus status;
295
0
                rc = LzmaDec_DecodeToBuf(&drec->state, drec->stream.next_out, &outprocessed,
296
0
                                          drec->stream.next_in, &inprocessed, LZMA_FINISH_ANY, &status, d->tx->cfg->lzma_memlimit);
297
0
                drec->stream.avail_in -= inprocessed;
298
0
                drec->stream.next_in += inprocessed;
299
0
                drec->stream.avail_out -= outprocessed;
300
0
                drec->stream.next_out += outprocessed;
301
0
                switch (rc) {
302
0
                    case SZ_OK:
303
0
                        rc = Z_OK;
304
0
                        if (status == LZMA_STATUS_FINISHED_WITH_MARK) {
305
0
                            rc = Z_STREAM_END;
306
0
                        }
307
0
                        break;
308
0
                    case SZ_ERROR_MEM:
309
0
                        htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "LZMA decompressor: memory limit reached");
310
                        // fall through
311
0
                    default:
312
0
                        rc = Z_DATA_ERROR;
313
0
                }
314
0
            }
315
363k
        } else if (drec->zlib_initialized) {
316
356k
            rc = inflate(&drec->stream, Z_NO_FLUSH);
317
356k
        } else {
318
            // no initialization means previous error on stream
319
7.30k
            return HTP_ERROR;
320
7.30k
        }
321
356k
        int error_after_data = (rc == Z_DATA_ERROR && drec->restart == 0 && GZIP_BUF_SIZE > drec->stream.avail_out);
322
356k
        if (rc == Z_STREAM_END || error_after_data) {
323
            // How many bytes do we have?
324
23.4k
            size_t len = GZIP_BUF_SIZE - drec->stream.avail_out;
325
326
            // Update CRC
327
328
            // Prepare data for the callback.
329
23.4k
            htp_tx_data_t d2;
330
23.4k
            d2.tx = d->tx;
331
23.4k
            d2.data = drec->buffer;
332
23.4k
            d2.len = len;
333
23.4k
            d2.is_last = d->is_last;
334
335
23.4k
            if (drec->super.next != NULL && drec->zlib_initialized) {
336
14.2k
                callback_rc = htp_gzip_decompressor_decompress(drec->super.next, &d2);
337
14.2k
            } else {
338
                // Send decompressed data to the callback.
339
9.21k
                callback_rc = drec->super.callback(&d2);
340
9.21k
            }
341
23.4k
            if (callback_rc != HTP_OK) {
342
1.13k
                htp_gzip_decompressor_end(drec);
343
1.13k
                return callback_rc;
344
1.13k
            }
345
22.3k
            drec->stream.avail_out = GZIP_BUF_SIZE;
346
22.3k
            drec->stream.next_out = drec->buffer;
347
            // TODO Handle trailer.
348
349
22.3k
            if (error_after_data) {
350
                // There is data even if there is an error
351
                // So use this data and log a warning
352
3.53k
                htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "GZip decompressor: inflate failed with %d", rc);
353
3.53k
                if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
354
0
                    LzmaDec_Free(&drec->state, &lzma_Alloc);
355
0
                }
356
3.53k
                drec->zlib_initialized = HTP_COMPRESSION_OVER;
357
3.53k
                return HTP_ERROR;
358
3.53k
            }
359
18.7k
            return HTP_OK;
360
22.3k
        }
361
333k
        else if (rc != Z_OK) {
362
33.1k
            htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "GZip decompressor: inflate failed with %d", rc);
363
33.1k
            if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
364
0
                LzmaDec_Free(&drec->state, &lzma_Alloc);
365
                // so as to clean zlib ressources after restart
366
0
                drec->zlib_initialized = HTP_COMPRESSION_NONE;
367
33.1k
            } else {
368
33.1k
                inflateEnd(&drec->stream);
369
33.1k
            }
370
371
            // see if we want to restart the decompressor
372
33.1k
            if (htp_gzip_decompressor_restart(drec,
373
33.1k
                                              d->data, d->len, &consumed) == 1)
374
26.0k
            {
375
                // we'll be restarting the compressor
376
26.0k
                goto restart;
377
26.0k
            }
378
379
7.09k
            drec->zlib_initialized = 0;
380
381
            // all our inflate attempts have failed, simply
382
            // pass the raw data on to the callback in case
383
            // it's not compressed at all
384
385
7.09k
            htp_tx_data_t d2;
386
7.09k
            d2.tx = d->tx;
387
7.09k
            d2.data = d->data;
388
7.09k
            d2.len = d->len;
389
7.09k
            d2.is_last = d->is_last;
390
391
7.09k
            callback_rc = drec->super.callback(&d2);
392
7.09k
            if (callback_rc != HTP_OK) {
393
10
                return HTP_ERROR;
394
10
            }
395
396
7.08k
            drec->stream.avail_out = GZIP_BUF_SIZE;
397
7.08k
            drec->stream.next_out = drec->buffer;
398
399
            /* successfully passed through, lets continue doing that */
400
7.08k
            drec->super.passthrough = 1;
401
7.08k
            return HTP_OK;
402
7.09k
        }
403
356k
    }
404
405
42.9k
    return HTP_OK;
406
108k
}
407
408
/**
409
 * Shut down gzip decompressor.
410
 *
411
 * @param[in] drec
412
 */
413
18.8k
void htp_gzip_decompressor_destroy(htp_decompressor_t *drec1) {
414
18.8k
    htp_decompressor_gzip_t *drec = (htp_decompressor_gzip_t*) drec1;
415
18.8k
    if (drec == NULL) return;
416
417
18.8k
    htp_gzip_decompressor_end(drec);
418
419
18.8k
    free(drec->buffer);
420
18.8k
    free(drec);
421
18.8k
}
422
423
/**
424
 * Create a new decompressor instance.
425
 *
426
 * @param[in] connp
427
 * @param[in] format
428
 * @return New htp_decompressor_t instance on success, or NULL on failure.
429
 */
430
18.8k
htp_decompressor_t *htp_gzip_decompressor_create(htp_connp_t *connp, enum htp_content_encoding_t format) {
431
18.8k
    htp_decompressor_gzip_t *drec = calloc(1, sizeof (htp_decompressor_gzip_t));
432
18.8k
    if (drec == NULL) return NULL;
433
434
18.8k
    drec->super.decompress = NULL;
435
18.8k
    drec->super.destroy = NULL;
436
18.8k
    drec->super.next = NULL;
437
438
18.8k
    drec->buffer = malloc(GZIP_BUF_SIZE);
439
18.8k
    if (drec->buffer == NULL) {
440
0
        free(drec);
441
0
        return NULL;
442
0
    }
443
444
    // Initialize zlib.
445
18.8k
    int rc;
446
447
18.8k
    switch (format) {
448
0
        case HTP_COMPRESSION_LZMA:
449
0
            if (connp->cfg->lzma_memlimit > 0 &&
450
0
                connp->cfg->response_lzma_layer_limit > 0) {
451
0
                LzmaDec_Construct(&drec->state);
452
0
            } else {
453
0
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "LZMA decompression disabled");
454
0
                drec->super.passthrough = 1;
455
0
            }
456
0
            rc = Z_OK;
457
0
            break;
458
8.19k
        case HTP_COMPRESSION_DEFLATE:
459
            // Negative values activate raw processing,
460
            // which is what we need for deflate.
461
8.19k
            rc = inflateInit2(&drec->stream, -15);
462
8.19k
            break;
463
10.6k
        case HTP_COMPRESSION_GZIP:
464
            // Increased windows size activates gzip header processing.
465
10.6k
            rc = inflateInit2(&drec->stream, 15 + 32);
466
10.6k
            break;
467
0
        default:
468
            // do nothing
469
0
            rc = Z_DATA_ERROR;
470
18.8k
    }
471
472
18.8k
    if (rc != Z_OK) {
473
0
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "GZip decompressor: inflateInit2 failed with code %d", rc);
474
475
0
        if (format == HTP_COMPRESSION_DEFLATE || format == HTP_COMPRESSION_GZIP) {
476
0
            inflateEnd(&drec->stream);
477
0
        }
478
0
        free(drec->buffer);
479
0
        free(drec);
480
481
0
        return NULL;
482
0
    }
483
484
18.8k
    drec->zlib_initialized = format;
485
18.8k
    drec->stream.avail_out = GZIP_BUF_SIZE;
486
18.8k
    drec->stream.next_out = drec->buffer;
487
488
    #if 0
489
    if (format == COMPRESSION_DEFLATE) {
490
        drec->initialized = 1;
491
    }
492
    #endif
493
494
18.8k
    return (htp_decompressor_t *) drec;
495
18.8k
}