14 KiB
Security Audit: Montana Network Layer (net/)
Модель: GPT-5.2 Компания: OpenAI Дата: 08.01.2026 14:54 UTC
1. Понимание архитектуры
Montana — это ACP: сеть фиксирует «присутствие во времени» через регулярные подписи (τ₁/τ₂), которые распространяются в реальном времени по P2P. Безопасность консенсуса опирается на (a) криптографическую неподделываемость, (b) привязку к текущему τ₂ (prev_hash / сетевое окно), (c) eventual consistency через fork-choice.
Следствие для сетевого слоя: ключевая цель сети — обеспечить доставку и валидацию внешних данных (подписей/слайсов/tx) в bounded-режиме и не дать атакующему:
- изолировать узел на старте (bootstrap) или во время работы (eclipse/addrman poisoning)
- исчерпать ресурсы (mem/cpu/bandwidth) через сообщения, очереди, инвентори
- подменить доверенные источники на старте (hardcoded bootstrap anchors)
2. Изученные файлы
Сетевой слой montana/src/net/ (12,330 LOC):
net/message.rs(168)net/encrypted.rs(570)net/types.rs(658)net/serde_safe.rs(266)net/protocol.rs(1506)net/verification.rs(848)net/dns.rs(266)net/feeler.rs(256)net/discouraged.rs(286)net/sync.rs(665)net/bootstrap.rs(1413)net/startup.rs(184)net/subnet.rs(444)net/eviction.rs(347)net/mod.rs(82)net/inventory.rs(705)net/hardcoded_identity.rs(203)net/noise.rs(898)net/rate_limit.rs(879)net/connection.rs(509)net/peer.rs(438)net/addrman.rs(739)
Связанные файлы:
src/types.rs,src/crypto.rs,src/db.rs,src/nmi.rs,src/nts.rs
Инструменты/утилиты:
src/bin/attacker.rs(stress-тест)
3. Attack Surface
-
Bootstrap / Startup verification:
net/startup.rs→net/verification.rs→net/bootstrap.rs.- hardcoded challenge-response
- сбор P2P адресов через GetAddr / Addr
- принятие network time (median) и median height
-
Долгоживущая P2P-сеть:
net/protocol.rs.- inbound/outbound TCP
- Noise XX + ML-KEM (шифрование/аутентификация транспорта)
- обработка сообщений (Version/Verack/Addr/Inv/GetData/Slice/Tx/Presence)
-
Механизмы устойчивости:
- connection limits / netgroup / per-IP (
net/connection.rs) - eviction защищённых слотов (
net/eviction.rs) - AddrMan bucket system (
net/addrman.rs) + feeler (net/feeler.rs) - rate limit per peer + subnet-level (fast/slow) (
net/rate_limit.rs) - bounded collections (
net/serde_safe.rs,net/message.rs)
- connection limits / netgroup / per-IP (
4. Найденные уязвимости
[CRITICAL] Bootstrap MITM: подпись hardcoded узла не привязана к VersionPayload
Суть: протокол AuthChallenge/AuthResponse предполагает, что hardcoded узел доказывает идентичность подписью, но в коде проверяется подпись только над challenge, а поля version (высота, timestamp, best_slice, etc.) не входят в подписанное сообщение.
Уязвимый код (проверка подписи по одному challenge):
montana/src/net/verification.rs:493-515— подпись проверяется функциейverify_hardcoded_response(&challenge, &signature)и затем используетсяversion.montana/src/net/hardcoded_identity.rs:85-112—verify_hardcoded_response()проверяетverify_mldsa65(pubkey, challenge, signature).
Уязвимый код (hardcoded узел подписывает только challenge):
montana/src/net/protocol.rs:1175-1204—sign_mldsa65(secret_key, &challenge)и отправкаAuthResponse { version, signature }.
Почему это важно: на старте именно bootstrap-верификация определяет, чему узел доверяет как «правде» о сети (высота/время/seed peers). Если подпись не связывает version с challenge, атакующий «на проводе» может сохранить валидную подпись на challenge, но подменить VersionPayload (с высотой/временем/параметрами) в транзите.
Импакт: высокий риск некорректного bootstrap (неверная высота/время/peer-discovery), что может привести к:
- изоляции узла (eclipse-like) на этапе запуска
- выбору неправильного chain-tip/сегмента сети
- отказу в запуске из‑за «clock divergence» или «height divergence» (liveness)
Сложность: средняя для сетевого противника на пути (ISP/BGP/локальная сеть) или при наличии вредоносного прокси; не требует компрометации приватного ключа hardcoded узла.
Проверка председателя: ПОДТВЕРЖДЕНО по коду: подпись проверяется только над challenge и никак не включает version.
Рекомендация исправления:
- Подписывать и проверять контекст + challenge + сериализованный VersionPayload (и/или transcript):
sig_msg = "Montana.HardcodedAuth.v1" || challenge || postcard(version)- проверять
verify_mldsa65(pubkey, sig_msg, signature)
- Дополнительно: рассмотреть перенос bootstrap-канала поверх
EncryptedStream(Noise), чтобы исключить MITM в принципе.
[HIGH] Централизация bootstrap: фактически 1 hardcoded IP и пустые DNS seeds
Суть: документы декларируют набор seed/hardcoded узлов и их разнообразие. В текущем коде:
- DNS seeds пусты (комментарии-заглушки)
- fallback IP содержит 1 адрес
- hardcoded идентичности mainnet содержат 1 узел
Доказательства:
montana/src/net/dns.rs:8-32—DNS_SEEDSпустой,FALLBACK_IPSсодержит один адрес.montana/src/net/hardcoded_identity.rs:40-66—MAINNET_HARDCODEDсодержит один узел.
Импакт: при таком наборе «trusted core» становится фактически единственной точкой отказа:
- DoS/недоступность узла → bootstrap деградирует/ломается
- регуляторные/инфраструктурные события вокруг одного провайдера/географии → системный риск
Сложность: низкая (противнику достаточно бить в доступность одного узла).
Проверка председателя: ПОДТВЕРЖДЕНО по коду.
Рекомендация: расширить список hardcoded узлов (минимум 5–10), реально включить DNS seeds, обеспечить юрисдикционное/AS/гео разнообразие.
[MEDIUM] Несогласованность лимитов размера сообщений: в protocol.rs глобальный лимит = MAX_TX_SIZE (1MB), а в net/types.rs заявлен 2MB и inv до ~1.8MB
Суть: net/types.rs декларирует MESSAGE_SIZE_LIMIT = 2MB и MAX_INV_MSG_SIZE ≈ 1.8MB, но protocol.rs делает ранний reject любых payload > MAX_TX_SIZE (1MB) независимо от команды.
Доказательства:
montana/src/net/types.rs:111-140— 2MB лимит иMAX_INV_MSG_SIZE.montana/src/net/protocol.rs:1249-1263и1329-1334— ранняя проверкаif len > MAX_TX_SIZE.
Импакт: функциональная несовместимость со «спецификацией» внутри самого репо:
- крупные
invсообщения по верхней границе фактически не проходят - возможна деградация синхронизации/relay при больших батчах
Сложность: низкая (это детерминированное поведение кода).
Проверка председателя: ПОДТВЕРЖДЕНО по коду.
Рекомендация:
- заменить ранний лимит на
MESSAGE_SIZE_LIMIT, либо - привести
MAX_*константы к тому, что реально разрешено ранней проверкой.
[MEDIUM] ip_votes заявлен как bounded, но не очищается на disconnect → потенциальный рост памяти
Суть: комментарий говорит «max size = MAX_OUTBOUND», но ключи SocketAddr добавляются при каждом (outbound) Version и нигде не удаляются при разрыве соединения.
Доказательства:
- Определение + заявление bounded:
montana/src/net/protocol.rs:210-216. - Вставка голоса:
montana/src/net/protocol.rs:884-914. - Cleanup на disconnect удаляет только
sent_nonces, но неip_votes:montana/src/net/protocol.rs:837-844.
Импакт: потенциальный медленный рост HashMap при длительной работе и смене outbound адресов.
Сложность: низкая (естественная динамика сети + перезапросы).
Проверка председателя: ПОДТВЕРЖДЕНО по коду.
Рекомендация:
- удалять
ip_votes[peer.addr]приPeerDisconnected/disconnect, - либо хранить bounded LRU на
MAX_OUTBOUNDпоследних адресов.
[LOW] Хранение Noise static secret key не соответствует комментарию («encrypted»), хранится как raw 32 bytes
Суть: в комментарии указано «encrypted with a derived key», но фактически ключ пишется как std::fs::write(noise_key.bin, keypair.secret).
Доказательства:
montana/src/net/encrypted.rs:406-435.
Импакт: риск при локальной компрометации/backup leaks; смягчается правами 0600.
Сложность: зависит от локального противника.
Проверка председателя: ПОДТВЕРЖДЕНО по коду.
Рекомендация: либо зашифровать ключ на диске (например, OS keychain), либо поправить комментарий, чтобы не вводил в заблуждение.
5. Атаки, которые НЕ работают (или работают хуже ожидаемого)
-
«Просто спамить большие inv до 1.8MB»: верхняя граница
MAX_INV_MSG_SIZEтеоретически есть, ноprotocol.rsрежет всё >1MB ранней проверкой (MAX_TX_SIZE). -
src/bin/attacker.rsкак готовый эксплойт: этот бинарь сериализует сообщения черезbincodeи работает без Noise, тогда как текущая сеть вprotocol.rsиспользуетpostcardповерхEncryptedStream(Noise). В текущем виде это скорее исторический stress-тест/заготовка и не отражает реальный on-wire протокол.
6. Рекомендации
-
Закрыть CRITICAL bootstrap MITM:
- изменить
AuthResponseтак, чтобы подпись покрывалаchallenge || VersionPayloadс domain separation. - (опционально, сильнее) проводить
VerificationClientповерхEncryptedStream.
- изменить
-
Убрать single point of failure в bootstrap:
- заполнить
DNS_SEEDS, расширитьFALLBACK_IPS, добавить 5–10+ hardcoded identities.
- заполнить
-
Согласовать лимиты сообщений:
- использовать
MESSAGE_SIZE_LIMITкак ранний лимит вprotocol.rs/verification.rsили уменьшить max-команды.
- использовать
-
Сделать
ip_votesреально bounded:- удаление на disconnect или bounded LRU.
-
Уточнить/исправить хранение Noise secret:
- либо реализовать шифрование на диске, либо исправить комментарий.
7. Вердикт
[x] CRITICAL — есть уязвимость, позволяющая сломать доверие bootstrap (MITM при старте) [ ] HIGH — есть серьёзные уязвимости [ ] MEDIUM — есть уязвимости среднего риска [ ] LOW — только minor issues [ ] SECURE — уязвимостей не найдено