Описание
MQTT retained message store has no size or count limit and is never reclaimed
Summary
Any authenticated MQTT client with ordinary write permission can retain-publish to unlimited distinct
topics. The retained store has no count or size cap, is held in resident memory, and is never reclaimed;
it survives restart and cannot be enumerated or bulk-purged. Filling it latches the node's memory alarm
permanently, after which no MQTT client can connect at all.
Affected
deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_{dets,ets}.erl- Requires the
rabbitmq_mqttplugin (not enabled by default) - Ecosystem: Other: RabbitMQ Package:
rabbitmq-server - Confirmed on 4.3.4. Code unchanged since
6ac8d4def5(2015-04-23); wider range inferred, not tested.
Mechanism
insert/3 is an unconditional write: no count check, no size check, and no config setting exists to add
one (max_retained and equivalents do not exist anywhere in the plugin or its schema):
The default DETS backend is opened {ram_file, true}, keeping contents resident.
The store exports only start/insert/lookup/delete/expire/terminate. delete/2 needs an exact topic
name; there is no enumeration, no bulk purge, and no management API or UI surface. The only expiry is
MQTT 5's Message Expiry Interval — client-supplied and optional, so an attacker omits it.
Every comparable store in RabbitMQ has controls this one lacks: queues have max-length,
max-length-bytes, policy-applied TTL, overflow behaviour, UI visibility, one-click purge, and drain on
consumption. The retained store has none of them.
Reproduction
| Measurement | Before | After 300 × 1 MB | After restart |
|---|---|---|---|
| entries | 0 | 300 | 300 |
| store bytes | 5,464 | 618,665,790 | 618,665,790 |
erlang:memory(total) = 680,066,486, of which the store is 618,665,790 -> 91% of broker memory
(idle node: ~100–150 MB). All 300 accepted, no rejection or throttling. ~2x amplification: 300 MB
published becomes 618 MB permanently held.
Alarm and denial of service
With the watermark pinned low to shorten the test (set_vm_memory_high_watermark absolute "500MB"),
222 messages latched the alarm at 552 MB:
Every subsequent connection fails its handshake and is dropped at the login timeout. A client probe
returns MOSQ_ERR_CONN_LOST (exit 7). Still latched 4.5 minutes later with no publishing and no recovery.
Impact
While the alarm is latched, new MQTT clients cannot connect; not just publishers, not just the attacker's. The listener is dead to all clients until an operator intervenes.
Unlike queue backlog, this never self-heals: queues drain as consumers catch up and the alarm clears; retained messages never drain. The store is per-vhost, so write access in several vhosts multiplies it.
Limitations
- DETS files cap near 2 GB, so a single vhost's store is not literally unbounded. At ~2x amplification ~1 GB of publishing fills it; 2 GB unreclaimable resident memory still trips the default watermark on any node under ~3.3 GB, and the per-vhost scoping bypasses the single-table limit. ETS backend untested.
- Only 4.3.4 tested. Single node; cluster alarm propagation not measured.
- The alarm test pinned the watermark to 500 MB. This reduces the data volume required, not the mechanism.
Пакеты
rabbitmq
>= 4.3.0, < 4.3.5
4.3.5
6 Medium
CVSS4
Дефекты
6 Medium
CVSS4