fix(#72): harden codec -- bound BrokerListDecoder + validate slot range (C1)

Greens A1's adversarial tests, fixing the Gemini findings:
- BLOCKER (DoS): BrokerListDecoder drops out-of-range slots and caps total
  retained fields (brokerSlotCount*32), so a hostile BROKER_KV flood cannot
  grow memory without bound.
- MINOR: parse() rejects broker slots >= brokerSlotCount (was: stored slot 200).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/99/head
Strycher 4 weeks ago
parent e3a0873fd5
commit 5ba769e90a

@ -214,7 +214,10 @@ class ObserverConfigClient {
case rBrokersStart:
return ConfigBrokersStart(f.length > 2 ? f[2] : 0);
case rBrokerKv:
if (f.length < 3) return const ConfigUnknown(rBrokerKv);
// Reject out-of-range slots from a hostile device (contract: 0..N-1).
if (f.length < 3 || f[2] >= brokerSlotCount) {
return const ConfigUnknown(rBrokerKv);
}
final body = _text(f, 3);
final eq = body.indexOf(
'=',
@ -261,8 +264,14 @@ class ObserverConfigClient {
/// [BrokerConfig]s. Feed each parsed [ConfigResponse]; [add] returns the
/// decoded list once END arrives (or `[]` for an empty pool), else null.
class BrokerListDecoder {
/// Hard cap on total key/value lines retained across the whole dump a
/// hostile device cannot grow memory without bound (Gemini BLOCKER: DoS).
/// Generous for the 10-slot contract (~16 fields each).
static const int _maxFields = ObserverConfigClient.brokerSlotCount * 32;
final Map<int, Map<String, String>> _slots = {};
bool _open = false;
int _fields = 0;
/// Feed one response. Returns the broker list on END, else null.
/// Anything before START or after END is ignored.
@ -270,10 +279,18 @@ class BrokerListDecoder {
switch (r) {
case ConfigBrokersStart():
_slots.clear();
_fields = 0;
_open = true;
return null;
case ConfigBrokerKv(:final slot, :final key, :final value):
if (_open) (_slots[slot] ??= {})[key] = value;
// Drop out-of-range slots and stop accumulating past the field cap.
if (_open &&
slot >= 0 &&
slot < ObserverConfigClient.brokerSlotCount &&
_fields < _maxFields) {
(_slots[slot] ??= {})[key] = value;
_fields++;
}
return null;
case ConfigBrokersEnd():
if (!_open) return null;

Loading…
Cancel
Save

Powered by TurnKey Linux.