/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Fluent Bit * ========== * Copyright (C) 2015-2022 The Fluent Bit Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #define FLB_GZIP_HEADER_OFFSET 10 #define FLB_GZIP_HEADER_SIZE FLB_GZIP_HEADER_OFFSET #define FLB_GZIP_MAGIC_NUMBER 0x8B1F typedef enum { FTEXT = 1, FHCRC = 2, FEXTRA = 4, FNAME = 8, FCOMMENT = 16 } flb_tinf_gzip_flag; #pragma pack(push, 1) struct flb_gzip_header { uint16_t magic_number; uint8_t compression_method; uint8_t header_flags; uint32_t timestamp; uint8_t compression_flags; uint8_t operating_system_id; }; #pragma pack(pop) struct flb_gzip_decompression_context { struct flb_gzip_header gzip_header; mz_stream miniz_stream; }; static unsigned int read_le16(const unsigned char *p) { return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8); } static unsigned int read_le32(const unsigned char *p) { return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8) | ((unsigned int) p[2] << 16) | ((unsigned int) p[3] << 24); } static inline void gzip_header(void *buf) { uint8_t *p; /* GZip Magic bytes */ p = buf; *p++ = 0x1F; *p++ = 0x8B; *p++ = 8; *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0; *p++ = 0xFF; } #include static inline void flb_hex_dump(uint8_t *buffer, size_t buffer_length, size_t line_length) { char *printable_line; size_t buffer_index; size_t filler_index; if (40 < line_length) { line_length = 40; } printable_line = alloca(line_length + 1); if (NULL == printable_line) { printf("Alloca returned NULL\n"); return; } memset(printable_line, '\0', line_length + 1); for (buffer_index = 0 ; buffer_index < buffer_length ; buffer_index++) { if (0 != buffer_index && 0 == (buffer_index % line_length)) { printf("%s\n", printable_line); memset(printable_line, '\0', line_length + 1); } if (0 != isprint(buffer[buffer_index])) { printable_line[(buffer_index % line_length)] = buffer[buffer_index]; } else { printable_line[(buffer_index % line_length)] = '.'; } printf("%02X ", buffer[buffer_index]); } if (0 != buffer_index && 0 != (buffer_index % line_length)) { for (filler_index = 0 ; filler_index < (line_length - (buffer_index % line_length)) ; filler_index++) { printf(" "); } printf("%s\n", printable_line); memset(printable_line, '.', line_length); } } int flb_gzip_compress(void *in_data, size_t in_len, void **out_data, size_t *out_len) { int flush; int status; int footer_start; uint8_t *pb; size_t out_size; void *out_buf; z_stream strm; mz_ulong crc; /* * Calculating the upper bound for a gzip compression is * non-trivial, so we rely on miniz's own calculation * to guarantee memory safety. */ out_size = compressBound(in_len); out_buf = flb_malloc(out_size); if (!out_buf) { flb_errno(); flb_error("[gzip] could not allocate outgoing buffer"); return -1; } /* Initialize streaming buffer context */ memset(&strm, '\0', sizeof(strm)); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_in = in_data; strm.avail_in = in_len; strm.total_out = 0; /* Deflate mode */ deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -Z_DEFAULT_WINDOW_BITS, 9, Z_DEFAULT_STRATEGY); /* * Miniz don't support GZip format directly, instead we will: * * - append manual GZip magic bytes * - deflate raw content * - append manual CRC32 data */ gzip_header(out_buf); /* Header offset */ pb = (uint8_t *) out_buf + FLB_GZIP_HEADER_OFFSET; flush = Z_NO_FLUSH; while (1) { strm.next_out = pb + strm.total_out; strm.avail_out = out_size - (pb - (uint8_t *) out_buf); if (strm.avail_in == 0) { flush = Z_FINISH; } status = deflate(&strm, flush); if (status == Z_STREAM_END) { break; } else if (status != Z_OK) { deflateEnd(&strm); return -1; } } if (deflateEnd(&strm) != Z_OK) { flb_free(out_buf); return -1; } *out_len = strm.total_out; /* Construct the gzip checksum (CRC32 footer) */ footer_start = FLB_GZIP_HEADER_OFFSET + *out_len; pb = (uint8_t *) out_buf + footer_start; crc = mz_crc32(MZ_CRC32_INIT, in_data, in_len); *pb++ = crc & 0xFF; *pb++ = (crc >> 8) & 0xFF; *pb++ = (crc >> 16) & 0xFF; *pb++ = (crc >> 24) & 0xFF; *pb++ = in_len & 0xFF; *pb++ = (in_len >> 8) & 0xFF; *pb++ = (in_len >> 16) & 0xFF; *pb++ = (in_len >> 24) & 0xFF; /* Set the real buffer size for the caller */ *out_len += FLB_GZIP_HEADER_OFFSET + 8; *out_data = out_buf; return 0; } /* Uncompress (inflate) GZip data */ int flb_gzip_uncompress(void *in_data, size_t in_len, void **out_data, size_t *out_len) { int status; uint8_t *p; void *out_buf; size_t out_size = 0; void *zip_data; size_t zip_len; unsigned char flg; unsigned int xlen, hcrc; unsigned int dlen, crc; mz_ulong crc_out; mz_stream stream; const unsigned char *start; /* Minimal length: header + crc32 */ if (in_len < 18) { flb_error("[gzip] unexpected content length"); return -1; } /* Magic bytes */ p = in_data; if (p[0] != 0x1F || p[1] != 0x8B) { flb_error("[gzip] invalid magic bytes"); return -1; } if (p[2] != 8) { flb_error("[gzip] invalid method"); return -1; } /* Flag byte */ flg = p[3]; /* Reserved bits */ if (flg & 0xE0) { flb_error("[gzip] invalid flag"); return -1; } /* Skip base header of 10 bytes */ start = p + FLB_GZIP_HEADER_OFFSET; /* Skip extra data if present */ if (flg & FEXTRA) { xlen = read_le16(start); if (xlen > in_len - 12) { flb_error("[gzip] invalid gzip data"); return -1; } start += xlen + 2; } /* Skip file name if present */ if (flg & FNAME) { do { if (start - p >= in_len) { flb_error("[gzip] invalid gzip data (FNAME)"); return -1; } } while (*start++); } /* Skip file comment if present */ if (flg & FCOMMENT) { do { if (start - p >= in_len) { flb_error("[gzip] invalid gzip data (FCOMMENT)"); return -1; } } while (*start++); } /* Check header crc if present */ if (flg & FHCRC) { if (start - p > in_len - 2) { flb_error("[gzip] invalid gzip data (FHRC)"); return -1; } hcrc = read_le16(start); crc = mz_crc32(MZ_CRC32_INIT, p, start - p) & 0x0000FFFF; if (hcrc != crc) { flb_error("[gzip] invalid gzip header CRC"); return -1; } start += 2; } /* Get decompressed length */ dlen = read_le32(&p[in_len - 4]); /* Limit decompressed length to 100MB */ if (dlen > 100000000) { flb_error("[gzip] maximum decompression size is 100MB"); return -1; } /* Get CRC32 checksum of original data */ crc = read_le32(&p[in_len - 8]); /* Decompress data */ if ((p + in_len) - p < 8) { flb_error("[gzip] invalid gzip CRC32 checksum"); return -1; } /* Allocate outgoing buffer */ out_buf = flb_malloc(dlen); if (!out_buf) { flb_errno(); return -1; } out_size = dlen; /* Ensure size is above 0 */ if (((p + in_len) - start - 8) <= 0) { flb_free(out_buf); return -1; } /* Map zip content */ zip_data = (uint8_t *) start; zip_len = (p + in_len) - start - 8; memset(&stream, 0, sizeof(stream)); stream.next_in = zip_data; stream.avail_in = zip_len; stream.next_out = out_buf; stream.avail_out = out_size; status = mz_inflateInit2(&stream, -Z_DEFAULT_WINDOW_BITS); if (status != MZ_OK) { flb_free(out_buf); return -1; } status = mz_inflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); flb_free(out_buf); return -1; } if (stream.total_out != dlen) { mz_inflateEnd(&stream); flb_free(out_buf); flb_error("[gzip] invalid gzip data size"); return -1; } /* terminate the stream, it's not longer required */ mz_inflateEnd(&stream); /* Validate message CRC vs inflated data CRC */ crc_out = mz_crc32(MZ_CRC32_INIT, out_buf, dlen); if (crc_out != crc) { flb_free(out_buf); flb_error("[gzip] invalid GZip checksum (CRC32)"); return -1; } /* set the uncompressed data */ *out_len = dlen; *out_data = out_buf; return 0; } /* Stateful gzip decompressor */ static int flb_gzip_decompressor_process_header( struct flb_decompression_context *context) { struct flb_gzip_decompression_context *inner_context; inner_context = (struct flb_gzip_decompression_context *) \ context->inner_context; /* Minimal length: header + crc32 */ if (context->input_buffer_length < FLB_GZIP_HEADER_SIZE) { flb_error("[gzip] unexpected content length"); return FLB_DECOMPRESSOR_FAILURE; } memcpy(&inner_context->gzip_header, context->read_buffer, FLB_GZIP_HEADER_SIZE); context->read_buffer = &context->read_buffer[FLB_GZIP_HEADER_SIZE]; context->input_buffer_length -= FLB_GZIP_HEADER_SIZE; /* Magic bytes */ if (inner_context->gzip_header.magic_number != FLB_GZIP_MAGIC_NUMBER) { context->state = FLB_DECOMPRESSOR_STATE_FAILED; flb_error("[gzip] invalid magic bytes : %04x", inner_context->gzip_header.magic_number); return FLB_DECOMPRESSOR_FAILURE; } if (inner_context->gzip_header.compression_method != MZ_DEFLATED) { context->state = FLB_DECOMPRESSOR_STATE_FAILED; flb_error("[gzip] invalid method : %u", inner_context->gzip_header.compression_method); return FLB_DECOMPRESSOR_FAILURE; } /* Flag processing */ /* Reserved bits */ if (inner_context->gzip_header.header_flags & 0xE0) { context->state = FLB_DECOMPRESSOR_STATE_FAILED; flb_error("[gzip] invalid flag mask : %x", inner_context->gzip_header.header_flags); return FLB_DECOMPRESSOR_FAILURE; } context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_OPTIONAL_HEADERS; return FLB_DECOMPRESSOR_SUCCESS; } static int flb_gzip_decompressor_process_optional_headers( struct flb_decompression_context *context) { struct flb_gzip_decompression_context *inner_context; int status; uint16_t hcrc; uint16_t xlen; uint16_t crc; inner_context = (struct flb_gzip_decompression_context *) \ context->inner_context; /* Skip extra data if present */ if (inner_context->gzip_header.header_flags & FEXTRA) { if (context->input_buffer_length <= sizeof(uint16_t)) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } xlen = sizeof(uint16_t) + read_le16(context->read_buffer); if (context->input_buffer_length < xlen) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } context->read_buffer = &context->read_buffer[xlen]; context->input_buffer_length -= xlen; inner_context->gzip_header.header_flags &= (~FEXTRA); } if (inner_context->gzip_header.header_flags != 0 && context->input_buffer_length == 0) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } /* Skip file name if present */ if (inner_context->gzip_header.header_flags & FNAME) { xlen = strnlen((char *) context->read_buffer, context->input_buffer_length); if (xlen == 0 || xlen == context->input_buffer_length) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } xlen++; context->read_buffer = &context->read_buffer[xlen]; context->input_buffer_length -= xlen; inner_context->gzip_header.header_flags &= (~FNAME); } if (inner_context->gzip_header.header_flags != 0 && context->input_buffer_length == 0) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } /* Skip file comment if present */ if (inner_context->gzip_header.header_flags & FCOMMENT) { xlen = strnlen((char *) context->read_buffer, context->input_buffer_length); if (xlen == 0 || xlen == context->input_buffer_length) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } context->read_buffer = &context->read_buffer[xlen]; context->input_buffer_length -= xlen; inner_context->gzip_header.header_flags &= (~FCOMMENT); } if (inner_context->gzip_header.header_flags != 0 && context->input_buffer_length == 0) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } /* Check header crc if present (lower 16 bits of the checksum)*/ if (inner_context->gzip_header.header_flags & FHCRC) { if (context->input_buffer_length <= sizeof(uint16_t)) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } hcrc = read_le16(context->read_buffer); crc = mz_crc32(MZ_CRC32_INIT, (const unsigned char *) &inner_context->gzip_header, FLB_GZIP_HEADER_SIZE); crc &= 0x0000FFFF; if (hcrc != crc) { context->state = FLB_DECOMPRESSOR_STATE_FAILED; return FLB_DECOMPRESSOR_CORRUPTED_HEADER; } xlen = sizeof(uint16_t); context->read_buffer = &context->read_buffer[xlen]; context->input_buffer_length -= xlen; inner_context->gzip_header.header_flags &= (~FHCRC); } status = mz_inflateInit2(&inner_context->miniz_stream, -Z_DEFAULT_WINDOW_BITS); if (status != MZ_OK) { context->state = FLB_DECOMPRESSOR_STATE_FAILED; return FLB_DECOMPRESSOR_FAILURE; } context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_BODY; return FLB_DECOMPRESSOR_SUCCESS; } static int flb_gzip_decompressor_process_body_chunk( struct flb_decompression_context *context, void *output_buffer, size_t *output_length) { size_t processed_bytes; struct flb_gzip_decompression_context *inner_context; int status; if (*output_length == 0) { return FLB_DECOMPRESSOR_SUCCESS; } inner_context = (struct flb_gzip_decompression_context *) \ context->inner_context; inner_context->miniz_stream.next_in = context->read_buffer; inner_context->miniz_stream.avail_in = context->input_buffer_length; inner_context->miniz_stream.next_out = output_buffer; inner_context->miniz_stream.avail_out = *output_length; status = mz_inflate(&inner_context->miniz_stream, MZ_PARTIAL_FLUSH); if (status != MZ_OK && status != MZ_STREAM_END) { context->state = FLB_DECOMPRESSOR_STATE_FAILED; mz_inflateEnd(&inner_context->miniz_stream); *output_length = 0; return FLB_DECOMPRESSOR_FAILURE; } processed_bytes = context->input_buffer_length;; processed_bytes -= inner_context->miniz_stream.avail_in; *output_length -= inner_context->miniz_stream.avail_out; #ifdef FLB_DECOMPRESSOR_ERASE_DECOMPRESSED_DATA if (processed_bytes > 0) { memset(context->read_buffer, processed_bytes); } #endif context->read_buffer = &context->read_buffer[processed_bytes]; context->input_buffer_length = inner_context->miniz_stream.avail_in; if (status == MZ_STREAM_END) { mz_inflateEnd(&inner_context->miniz_stream); context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_FOOTER; memset(&inner_context->miniz_stream, 0, sizeof(mz_stream)); } return FLB_DECOMPRESSOR_SUCCESS; } static int flb_gzip_decompressor_process_footer( struct flb_decompression_context *context) { if (context->input_buffer_length < (sizeof(uint32_t) * 2)) { return FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } context->input_buffer_length -= (sizeof(uint32_t) * 2); if (context->input_buffer_length > 0) { context->read_buffer = &context->read_buffer[sizeof(uint32_t) * 2]; } else { context->read_buffer = context->input_buffer; } context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_HEADER; return FLB_DECOMPRESSOR_SUCCESS; } int flb_gzip_decompressor_dispatch(struct flb_decompression_context *context, void *output_buffer, size_t *output_length) { size_t output_buffer_size; int status; output_buffer_size = *output_length; *output_length = 0; status = FLB_DECOMPRESSOR_SUCCESS; if (context == NULL || context->inner_context == NULL) { status = FLB_DECOMPRESSOR_FAILURE; } if (context->input_buffer_length == 0) { flb_debug("[gzip] unexpected call with an empty input buffer"); status = FLB_DECOMPRESSOR_INSUFFICIENT_DATA; } if (status == FLB_DECOMPRESSOR_SUCCESS && context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_HEADER) { status = flb_gzip_decompressor_process_header(context); } if (status == FLB_DECOMPRESSOR_SUCCESS && context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_OPTIONAL_HEADERS) { status = flb_gzip_decompressor_process_optional_headers(context); } if (status == FLB_DECOMPRESSOR_SUCCESS && context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_BODY) { *output_length = output_buffer_size; status = flb_gzip_decompressor_process_body_chunk( context, output_buffer, output_length); } if (status == FLB_DECOMPRESSOR_SUCCESS && context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_FOOTER) { status = flb_gzip_decompressor_process_footer(context); } return status; } void *flb_gzip_decompression_context_create() { struct flb_gzip_decompression_context *context; context = flb_calloc(1, sizeof(struct flb_gzip_decompression_context)); if (context == NULL) { flb_errno(); } return (void *) context; } void flb_gzip_decompression_context_destroy(void *context) { if (context != NULL) { flb_free(context); } }