14 KiB
14 KiB
Модель: GPT-5.2 Компания: OpenAI Дата: 07.01.2026 10:49 UTC
Область анализа
Сетевой слой Montana (Rust): Montana ACP/montana/src/net/* + обработчики событий в montana/src/main.rs.
Фокус — векторы из Council/PROMPT_COUNSIL_TEMPLATE.md:
- Eclipse Attack
- Memory Exhaustion
- Connection Slot Exhaustion
- Sync DoS
- Rate Limit Bypass
TIER 0
1) Eclipse Attack
Что важно в протоколе
- Outbound-пиры считаются «более доверенными» (например, голосование за внешний IP учитывает только outbound;
net/protocol.rs, блокMIN_IP_VOTES). - Адресная книга (
AddrMan) персистентна (addresses.dat) и переживает рестарт.
Слабые места в коде
- Продвижение адреса в TRIED происходит до успешного handhsake
- В
net/protocol.rsпосле успешного TCP connect вызываетсяaddresses.mark_connected(&socket_addr)доhandle_connection()/завершения рукопожатия. - Следствие: «фейковая успешность» = достаточно принимать TCP, даже если Version/Verack не завершены или дальше соединение будет разорвано.
- Нет "anchor connections" между рестартами
- В коде присутствует только периодический
save(addresses.dat)иsave(banlist.dat)(например,net/protocol.rs::maintenance_loop), но нет отдельного механизма закрепления last-known-good outbound peers. - Это усиливает сценарий «ждать рестарт жертвы» из шаблона: после рестарта выбор адресов снова пойдёт через
AddrMan::select().
- Feeler/кеш ответов адресов заявлены, но не интегрированы
FeelerManagerиAddrResponseCacheсуществуют вnet/feeler.rs, но поиск по репозиторию показывает, что они нигде не используются (кроме re-export вnet/mod.rs).- Следствие: защита качества addr-таблицы/анти-фингерпринтинг по GetAddr выглядит «задекларированной», но фактически не работает.
- Пер-пирная лимитация Addr легко масштабируется Sybil’ом
Addrограничен на обработку через token bucket на peer (net/protocol.rs+net/rate_limit.rs).- Атакующий множит соединения → множит «первичный burst» (1000 адресов) и скорость пополнения.
Как эксплуатировать
-
Шаг A: Poison NEW
- Поднять N нод(ы) атакующего с IP в разных /16.
- На каждой — установить TCP listener и отвечать так, чтобы жертва могла подключиться.
- С каждого соединения (после handshake) слать
Addrс ~1000 адресов атакующей инфраструктуры. Повторять через новые соединения для обнуления token-bucket.
-
Шаг B: Продвинуть в TRIED через fake connections
- Добиться, чтобы жертва делала outbound dial на адреса атакующего.
- Достаточно, чтобы TCP connect прошёл: жертва вызывает
addresses.mark_connected()до handshake → адрес быстро попадает в TRIED. - На стороне атакующего можно не завершать handshake (или завершать, но не обслуживать sync), при этом адресная книга у жертвы «поверит».
-
Шаг C: Рестарт/перехват outbound
- После рестарта жертва выбирает адреса через
AddrMan::select()(50/50 new vs tried). - Если TRIED и NEW существенно заполнены адресами атакующего, вероятность outbound→attacker высокая.
- После рестарта жертва выбирает адреса через
Итоговый риск
- Высокий: ключевой баг — доверие к TCP connect как к «успешному соединению» для целей
mark_connected/mark_good.
2) Memory Exhaustion
Наблюдения по защитам
read_message()имеет ранний лимит размера кадра (≤MAX_SLICE_SIZE= 4MB) и bincodewith_limit(len)— это хорошо против «len-prefix alloc bomb».- Есть recv flow control до
read_message()(блокировка чтения при переполнении очереди) — это хорошо против некоторых классов memory DoS.
Слабые места в коде
Inventory::relay_cacheне ограничен по объёму/числу элементов
net/inventory.rs:relay_cache: HashMap<Hash, RelayEntry>растёт без cap, чистится только по TTL (RELAY_CACHE_EXPIRY_SECS).- Атакующий может присылать много уникальных
Slice/Tx/Presence→ жертва будет кэшировать сериализованные данные (Network::broadcast_*кэширует черезcache_relay()), потребление памяти растёт до O(rate × TTL).
- OrphanPool ограничен по количеству, но worst-case всё равно огромный
net/sync.rs:MAX_ORPHANS = 100, приMAX_SLICE_SIZE=4MBworst-case ≈ 400MB только на orphan’ах.- Если атакующий может заставить ноду принимать «слайсы без родителей» (зависит от валидации выше по стеку), это — практичный memory spike.
- Потенциально огромная очередь исходящих сообщений на per-peer channel
- В
net/protocol.rsна соединение создаётсяmpsc::channel::<Message>(1000). - В
main.rs::send_slices_to_peer()приNeedSlicesнода может отправить доcountслайсов подряд. countограничивается вprotocol.rsдо 500, но нет send-side flow control, аMessage::Sliceможет быть большим. Если удалённый peer перестанет читать, очередь/буферизация могут привести к значительной памяти.
Как эксплуатировать
- Relay-cache blowup: отправлять поток уникальных
Slice(илиTx) так, чтобы они попадали вcache_relay(); поддерживать rate выше, чемexpire()успевает чистить, в течение TTL. - Outbound send queue pressure: инициировать
GetSlices(см. Sync DoS) и затем не читать ответы/читать медленно, провоцируя накопление очереди и удержание больших объектов.
Итоговый риск
- Средний→Высокий (в зависимости от реального пути попадания больших объектов в relay_cache и от поведения sender при backpressure).
TIER 1
3) Connection Slot Exhaustion
Слабые места в коде
- Inbound слот учитывается сразу при accept, до handshake
net/protocol.rs::listener_loop: послеaccept()вызываетсяconnections.add_inbound(&addr)и только потом запускаетсяhandle_connection().- Handshake может длиться до 60 секунд (
HANDSHAKE_TIMEOUT_SECS=60,net/types.rs).
- Нет активной eviction-логики в runtime
net/eviction.rsреализуетselect_peer_to_evict(), но поиск показал отсутствие вызовов изprotocol.rs/connection.rs.- Когда inbound достигнут, нода просто «rejecting» новые соединения, но не освобождает старые.
- Нет rate limit на создание TCP соединений (per-IP/per-subnet) на входе
can_accept_inbound()проверяет только численные лимиты.
Как эксплуатировать
- Slow/No handshake (slot pinning):
- Открыть много inbound TCP соединений и не отправлять
Version(или отправлять неполный фрейм). - В
handle_connection()чтение pre-handshake обёрнуто вtimeout(30s, read_message()), но атакующему достаточно держать 117 слотов занятыми, переподключаясь по мере таймаута.
- Открыть много inbound TCP соединений и не отправлять
- Если есть много /16: из-за
MAX_PEERS_PER_NETGROUP=2для заполнения 117 inbound слотов потребуются адреса из множества /16 (IPv6 упрощает, если attacker контролирует префиксы).
Итоговый риск
- Высокий для доступности inbound-подключений (особенно против узлов, которым важны inbound).
4) Sync DoS
Слабые места в коде
GetSlicesне rate limited
- В
net/protocol.rsдляMessage::GetSlices { start, count }есть толькоcount.min(500). - Нет token bucket/пер-IP ограничений для этого типа запросов.
- Обработчик
NeedSlicesвmain.rsотвечает без доп. ограничений
main.rs::send_slices_to_peer()читает слайсы из хранилища и шлёт их подряд.- В результате один небольшой запрос может спровоцировать длительную отправку больших данных.
Как эксплуатировать
-
Bandwidth + IO exhaustion:
- После handshake слать
GetSlicesна диапазоны, которые у жертвы точно есть (например, начиная с genesis). - Повторять с разных соединений.
- На стороне атакующего можно не читать или читать медленно, усиливая нагрузку на буферы/очереди.
- После handshake слать
-
Амплификация:
- Даже с лимитом
count<=500, если средний слайс ~1–4MB, один запрос может требовать от жертвы до сотен МБ/ГБ исходящего трафика.
- Даже с лимитом
Итоговый риск
- Высокий (простая эксплуатация, высокая стоимость для жертвы).
5) Rate Limit Bypass
Слабые места в коде
- Rate limiting — per-connection/per-peer, а не per-IP/subnet
PeerRateLimitsхранится вPeerи создаётся на соединение (net/peer.rs,net/rate_limit.rs).- Атакующий масштабирует пропускную способность линейно числом соединений.
- Не все message types покрыты rate limiting’ом
- Лимитированы только
Addr/Inv/GetData. GetSlices/GetSlice/Ping/Pong/GetHeaders/SliceHeaders/...не имеют token bucket.
- Burst reset через переподключение
- Token buckets инициализируются с полной ёмкостью при создании
Peer. - Быстрое переподключение даёт снова полный burst (например, 1000 адресов для Addr).
Как эксплуатировать
- Addr poisoning в обход лимита: много коротких соединений → каждый раз отправлять
Addrна 1000 адресов. - Request flooding без лимитов: после handshake слать
GetSlices/Pingна высокой частоте.
Итоговый риск
- Высокий (мультипликативный эффект Sybil’а).
Короткие рекомендации (минимум для закрытия самых критичных дыр)
- Eclipse: перенос
addresses.mark_connected()/mark_good()на момент после успешного handshake (Version+Verack) и/или после минимального периода корректного поведения. - Sync DoS: добавить token bucket на
GetSlices/GetSlice/GetHeaders/SliceHeaders(как минимум per-peer, лучше per-IP/subnet) + лимитировать общий объём отправляемых данных на peer/время. - Slot exhaustion: учитывать inbound слот после handshake или держать отдельный лимит на "pre-handshake" соединения; добавить rate limit на accept/handshake по IP/subnet.
- Memory: ввести cap (по количеству и по суммарным байтам) на
relay_cache, а также send-side flow control/ограничение очереди на большие сообщения.