← THE INDEX  ·  WRITEUP

Persistent DoS via GZIP Compression Bomb in Aiven Karapace REST Proxy

An authenticated Kafka producer can crash the Karapace REST Proxy by writing a GZIP compression bomb to a topic, because librdkafka's GZIP codec has no decompression size limit while its ZSTD codec does.

Summary

An authenticated Aiven Kafka user can crash Aiven's Karapace REST Proxy by producing a GZIP-compressed message with a high compression ratio (compression bomb) to any topic, then consuming that topic through the Karapace REST API. Karapace uses confluent-kafka-python backed by librdkafka for its internal consumer. librdkafka's GZIP decompression codec performs no upper-bound check on output size, unlike its ZSTD codec which correctly caps decompression at receive.message.max.bytes. A 917 KB compressed message decompresses to 900 MB, causing Karapace to allocate 1.8 GB or more of memory and crash.

The bomb message persists in the Kafka topic until retention expires, so every subsequent consume request from any user re-triggers the crash. This makes the denial of service persistent and repeatable across Karapace restarts.

Impact

Karapace crash affects both the Schema Registry and the REST Proxy for the entire Kafka service, not just the attacker's consumer group. All users of the REST API are affected until Karapace restarts and the bomb message either expires or is removed. An attacker can write the bomb and leave; they do not need to stay connected.

The inconsistency in librdkafka is informative: ZSTD caps decompression at receive.message.max.bytes via an explicit check in rdkafka_zstd.c. GZIP (rdgz.c), LZ4 (rdkafka_lz4.c), and Snappy (snappy.c) perform no equivalent limit. Aiven's Karapace configuration adds no additional decompression size cap on top of librdkafka's defaults.

Root cause

The decompression codecs in librdkafka have inconsistent size bounds:

Codec Limit Vulnerable
ZSTD (rdkafka_zstd.c) while (out_bufsize <= recv_max_msg_size) No
GZIP (rdgz.c) None, allocates full strm.total_out Yes
LZ4 (rdkafka_lz4.c) None, unbounded realloc loop Yes
Snappy (snappy.c) None, allocates sum of chunk sizes Yes

Aiven's Karapace server-side consumer decompresses messages from the broker before serving them via the REST API. There is no additional size limit applied at the Karapace layer. A client that produces a highly compressible message (for example, 200 MB of zero bytes) stores only approximately 200 KB on the broker. When Karapace's librdkafka consumer reads and decompresses the message, it allocates the full 200 MB output with no bounds check.

Proof of concept

Step 1 produces a GZIP bomb to a Kafka topic using the native Kafka protocol. Step 2 triggers the crash by consuming through the Karapace REST API. All credentials and host identifiers have been replaced with placeholders.

Trigger and verify

Step 2 consumes through Karapace, triggering decompression and the OOM crash.

Disclosure and fix

Reported to Aiven through their bug bounty program. Recommended mitigations:

  1. Configure a safe receive.message.max.bytes value in the Karapace consumer and add explicit decompression size validation before serving messages via the REST API.
  2. Set a container memory limit on the Karapace process with an OOM restart policy to bound blast radius.
  3. Add a configurable max.decompressed.message.bytes parameter that applies across all compression codecs in the Karapace layer.
  4. Report the inconsistent decompression bounds to the librdkafka project so GZIP, LZ4, and Snappy receive the same size cap that ZSTD already enforces.