Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/libhtp/htp/htp_decompressors.c
Line
Count
Source
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
29.4k
{
56
29.4k
    if (data_len < 4)
57
2.43k
        return 0;
58
59
27.0k
    size_t consumed = 0;
60
61
27.0k
    if (data[0] == 0x1f && data[1] == 0x8b && data[3] != 0) {
62
9.31k
        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.51k
            size_t len;
70
122k
            for (len = 10; len < data_len && data[len] != '\0'; len++);
71
4.51k
            consumed = len + 1;
72
73
            //printf("skipped %u bytes for FNAME/FCOMMENT header (GZIP)\n", (uint)consumed);
74
75
4.79k
        } else if (data[3] & (1 << 1)) {
76
3.73k
            consumed = 12;
77
            //printf("skipped %u bytes for FHCRC header (GZIP)\n", 12);
78
79
3.73k
        } else {
80
            //printf("GZIP unknown/unsupported flags %02X\n", data[3]);
81
1.06k
            consumed = 10;
82
1.06k
        }
83
9.31k
    }
84
85
27.0k
    if (consumed > data_len)
86
4.20k
        return 0;
87
88
22.8k
    return consumed;
89
27.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
37.5k
{
99
37.5k
    size_t consumed = 0;
100
37.5k
    int rc = 0;
101
102
37.5k
    if (drec->restart < 3) {
103
104
        // first retry with the existing type, but now consider the
105
        // extensions
106
29.4k
        if (drec->restart == 0) {
107
10.6k
            consumed = htp_gzip_decompressor_probe(data, data_len);
108
109
10.6k
            if (drec->zlib_initialized == HTP_COMPRESSION_GZIP) {
110
                //printf("GZIP restart, consumed %u\n", (uint)consumed);
111
8.87k
                rc = inflateInit2(&drec->stream, 15 + 32);
112
8.87k
            } else {
113
                //printf("DEFLATE restart, consumed %u\n", (uint)consumed);
114
1.74k
                rc = inflateInit2(&drec->stream, -15);
115
1.74k
            }
116
10.6k
            if (rc != Z_OK)
117
0
                return 0;
118
119
10.6k
            goto restart;
120
121
        // if that still fails, try the other method we support
122
123
18.8k
        } else if (drec->zlib_initialized == HTP_COMPRESSION_DEFLATE) {
124
8.36k
            rc = inflateInit2(&drec->stream, 15 + 32);
125
8.36k
            if (rc != Z_OK)
126
0
                return 0;
127
128
8.36k
            drec->zlib_initialized = HTP_COMPRESSION_GZIP;
129
8.36k
            consumed = htp_gzip_decompressor_probe(data, data_len);
130
#if 0
131
            printf("DEFLATE -> GZIP consumed %u\n", (uint)consumed);
132
#endif
133
8.36k
            goto restart;
134
135
10.4k
        } else if (drec->zlib_initialized == HTP_COMPRESSION_GZIP) {
136
10.4k
            rc = inflateInit2(&drec->stream, -15);
137
10.4k
            if (rc != Z_OK)
138
0
                return 0;
139
140
10.4k
            drec->zlib_initialized = HTP_COMPRESSION_DEFLATE;
141
10.4k
            consumed = htp_gzip_decompressor_probe(data, data_len);
142
#if 0
143
            printf("GZIP -> DEFLATE consumed %u\n", (uint)consumed);
144
#endif
145
10.4k
            goto restart;
146
10.4k
        }
147
29.4k
    }
148
8.09k
    return 0;
149
150
29.4k
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
29.4k
    *consumed_back = consumed;
158
29.4k
    drec->restart++;
159
29.4k
    return 1;
160
37.5k
}
161
162
/**
163
 * Ends decompressor.
164
 *
165
 * @param[in] drec
166
 */
167
23.1k
static void htp_gzip_decompressor_end(htp_decompressor_gzip_t *drec) {
168
23.1k
    if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
169
0
        LzmaDec_Free(&drec->state, &lzma_Alloc);
170
0
        drec->zlib_initialized = 0;
171
23.1k
    } else if (drec->zlib_initialized) {
172
13.3k
        inflateEnd(&drec->stream);
173
13.3k
        drec->zlib_initialized = 0;
174
13.3k
    }
175
23.1k
}
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
465k
htp_status_t htp_gzip_decompressor_decompress(htp_decompressor_t *drec1, htp_tx_data_t *d) {
186
465k
    size_t consumed = 0;
187
465k
    int rc = 0;
188
465k
    htp_status_t callback_rc;
189
465k
    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
465k
    if (drec->super.passthrough) {
194
313k
        htp_tx_data_t d2;
195
313k
        d2.tx = d->tx;
196
313k
        d2.data = d->data;
197
313k
        d2.len = d->len;
198
313k
        d2.is_last = d->is_last;
199
200
313k
        callback_rc = drec->super.callback(&d2);
201
313k
        if (callback_rc != HTP_OK) {
202
760
            return HTP_ERROR;
203
760
        }
204
205
312k
        return HTP_OK;
206
313k
    } else if (drec->zlib_initialized == HTP_COMPRESSION_OVER) {
207
85.2k
        return HTP_ERROR;
208
85.2k
    }
209
210
67.0k
    if (d->data == NULL) {
211
        // Prepare data for callback.
212
4.79k
        htp_tx_data_t dout;
213
4.79k
        dout.tx = d->tx;
214
        // This is last call, so output uncompressed data so far
215
4.79k
        dout.len = GZIP_BUF_SIZE - drec->stream.avail_out;
216
4.79k
        if (dout.len > 0) {
217
916
            dout.data = drec->buffer;
218
3.88k
        } else {
219
3.88k
            dout.data = NULL;
220
3.88k
        }
221
4.79k
        dout.is_last = d->is_last;
222
4.79k
        if (drec->super.next != NULL && drec->zlib_initialized) {
223
527
            return htp_gzip_decompressor_decompress(drec->super.next, &dout);
224
4.27k
        } else {
225
            // Send decompressed data to the callback.
226
4.27k
            callback_rc = drec->super.callback(&dout);
227
4.27k
            if (callback_rc != HTP_OK) {
228
0
                htp_gzip_decompressor_end(drec);
229
0
                return callback_rc;
230
0
            }
231
4.27k
        }
232
233
4.27k
        return HTP_OK;
234
4.79k
    }
235
236
91.7k
restart:
237
91.7k
    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
91.7k
    drec->stream.next_in = (unsigned char *) (d->data + consumed);
242
91.7k
    drec->stream.avail_in = (uint32_t) (d->len - consumed);
243
244
309k
    while (drec->stream.avail_in != 0) {
245
        // If there's no more data left in the
246
        // buffer, send that information out.
247
277k
        if (drec->stream.avail_out == 0) {
248
187k
            drec->crc = crc32(drec->crc, drec->buffer, GZIP_BUF_SIZE);
249
250
            // Prepare data for callback.
251
187k
            htp_tx_data_t d2;
252
187k
            d2.tx = d->tx;
253
187k
            d2.data = drec->buffer;
254
187k
            d2.len = GZIP_BUF_SIZE;
255
187k
            d2.is_last = d->is_last;
256
257
187k
            if (drec->super.next != NULL && drec->zlib_initialized) {
258
4.06k
                callback_rc = htp_gzip_decompressor_decompress(drec->super.next, &d2);
259
183k
            } else {
260
                // Send decompressed data to callback.
261
183k
                callback_rc = drec->super.callback(&d2);
262
183k
            }
263
187k
            if (callback_rc != HTP_OK) {
264
354
                htp_gzip_decompressor_end(drec);
265
354
                return callback_rc;
266
354
            }
267
268
186k
            drec->stream.next_out = drec->buffer;
269
186k
            drec->stream.avail_out = GZIP_BUF_SIZE;
270
186k
        }
271
272
277k
        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
277k
        } else if (drec->zlib_initialized) {
316
269k
            rc = inflate(&drec->stream, Z_NO_FLUSH);
317
269k
        } else {
318
            // no initialization means previous error on stream
319
8.21k
            return HTP_ERROR;
320
8.21k
        }
321
269k
        int error_after_data = (rc == Z_DATA_ERROR && drec->restart == 0 && GZIP_BUF_SIZE > drec->stream.avail_out);
322
269k
        if (rc == Z_STREAM_END || error_after_data) {
323
            // How many bytes do we have?
324
13.5k
            size_t len = GZIP_BUF_SIZE - drec->stream.avail_out;
325
326
            // Update CRC
327
328
            // Prepare data for the callback.
329
13.5k
            htp_tx_data_t d2;
330
13.5k
            d2.tx = d->tx;
331
13.5k
            d2.data = drec->buffer;
332
13.5k
            d2.len = len;
333
13.5k
            d2.is_last = d->is_last;
334
335
13.5k
            if (drec->super.next != NULL && drec->zlib_initialized) {
336
3.02k
                callback_rc = htp_gzip_decompressor_decompress(drec->super.next, &d2);
337
10.5k
            } else {
338
                // Send decompressed data to the callback.
339
10.5k
                callback_rc = drec->super.callback(&d2);
340
10.5k
            }
341
13.5k
            if (callback_rc != HTP_OK) {
342
1.34k
                htp_gzip_decompressor_end(drec);
343
1.34k
                return callback_rc;
344
1.34k
            }
345
12.1k
            drec->stream.avail_out = GZIP_BUF_SIZE;
346
12.1k
            drec->stream.next_out = drec->buffer;
347
            // TODO Handle trailer.
348
349
12.1k
            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.51k
                htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "GZip decompressor: inflate failed with %d", rc);
353
3.51k
                if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
354
0
                    LzmaDec_Free(&drec->state, &lzma_Alloc);
355
0
                }
356
3.51k
                drec->zlib_initialized = HTP_COMPRESSION_OVER;
357
3.51k
                return HTP_ERROR;
358
3.51k
            }
359
8.68k
            return HTP_OK;
360
12.1k
        }
361
255k
        else if (rc != Z_OK) {
362
37.5k
            htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "GZip decompressor: inflate failed with %d", rc);
363
37.5k
            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
37.5k
            } else {
368
37.5k
                inflateEnd(&drec->stream);
369
37.5k
            }
370
371
            // see if we want to restart the decompressor
372
37.5k
            if (htp_gzip_decompressor_restart(drec,
373
37.5k
                                              d->data, d->len, &consumed) == 1)
374
29.4k
            {
375
                // we'll be restarting the compressor
376
29.4k
                goto restart;
377
29.4k
            }
378
379
8.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
8.09k
            htp_tx_data_t d2;
386
8.09k
            d2.tx = d->tx;
387
8.09k
            d2.data = d->data;
388
8.09k
            d2.len = d->len;
389
8.09k
            d2.is_last = d->is_last;
390
391
8.09k
            callback_rc = drec->super.callback(&d2);
392
8.09k
            if (callback_rc != HTP_OK) {
393
10
                return HTP_ERROR;
394
10
            }
395
396
8.08k
            drec->stream.avail_out = GZIP_BUF_SIZE;
397
8.08k
            drec->stream.next_out = drec->buffer;
398
399
            /* successfully passed through, lets continue doing that */
400
8.08k
            drec->super.passthrough = 1;
401
8.08k
            return HTP_OK;
402
8.09k
        }
403
269k
    }
404
405
32.0k
    return HTP_OK;
406
91.7k
}
407
408
/**
409
 * Shut down gzip decompressor.
410
 *
411
 * @param[in] drec
412
 */
413
21.4k
void htp_gzip_decompressor_destroy(htp_decompressor_t *drec1) {
414
21.4k
    htp_decompressor_gzip_t *drec = (htp_decompressor_gzip_t*) drec1;
415
21.4k
    if (drec == NULL) return;
416
417
21.4k
    htp_gzip_decompressor_end(drec);
418
419
21.4k
    free(drec->buffer);
420
21.4k
    free(drec);
421
21.4k
}
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
21.4k
htp_decompressor_t *htp_gzip_decompressor_create(htp_connp_t *connp, enum htp_content_encoding_t format) {
431
21.4k
    htp_decompressor_gzip_t *drec = calloc(1, sizeof (htp_decompressor_gzip_t));
432
21.4k
    if (drec == NULL) return NULL;
433
434
21.4k
    drec->super.decompress = NULL;
435
21.4k
    drec->super.destroy = NULL;
436
21.4k
    drec->super.next = NULL;
437
438
21.4k
    drec->buffer = malloc(GZIP_BUF_SIZE);
439
21.4k
    if (drec->buffer == NULL) {
440
0
        free(drec);
441
0
        return NULL;
442
0
    }
443
444
    // Initialize zlib.
445
21.4k
    int rc;
446
447
21.4k
    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.79k
        case HTP_COMPRESSION_DEFLATE:
459
            // Negative values activate raw processing,
460
            // which is what we need for deflate.
461
8.79k
            rc = inflateInit2(&drec->stream, -15);
462
8.79k
            break;
463
12.6k
        case HTP_COMPRESSION_GZIP:
464
            // Increased windows size activates gzip header processing.
465
12.6k
            rc = inflateInit2(&drec->stream, 15 + 32);
466
12.6k
            break;
467
0
        default:
468
            // do nothing
469
0
            rc = Z_DATA_ERROR;
470
21.4k
    }
471
472
21.4k
    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
21.4k
    drec->zlib_initialized = format;
485
21.4k
    drec->stream.avail_out = GZIP_BUF_SIZE;
486
21.4k
    drec->stream.next_out = drec->buffer;
487
488
    #if 0
489
    if (format == COMPRESSION_DEFLATE) {
490
        drec->initialized = 1;
491
    }
492
    #endif
493
494
21.4k
    return (htp_decompressor_t *) drec;
495
21.4k
}