27 KiB
Security Audit: Сетевой слой Montana
Модель: Composer 1 Компания: Cursor Дата: 15.01.2025 20:00 UTC
1. Понимание архитектуры
Montana использует принципиально иную модель консенсуса — Atemporal Coordinate Presence (ACP), основанную на физических ограничениях времени и вычислений, а не на традиционных механизмах блокчейна.
Ключевые отличия от традиционных систем:
-
Время как основа консенсуса: Montana использует физические ограничения Слоя -1 (упорядоченность событий) для создания временных координат (τ₂), которые физически невозможно подделать или ускорить. Это означает, что атаки типа "long-range" или "nothing-at-stake" не применимы, так как нельзя создать валидные координаты для прошлого времени.
-
Отсутствие майнеров/валидаторов: Вместо этого используется детерминистическая лотерея на основе хешей для выбора "победителей" слайсов. Без майнинга или стейкинга нет экономических стимулов для атак типа 51% или selfish mining.
-
Fork choice через время: Выбор форка основан на временной цепочке (timechain), где каждый слайс ссылается на предыдущий через хеш. Это делает реорганизации предсказуемыми и ограниченными временем.
-
Криптографическая безопасность: Все подписи используют ML-DSA-65 (пост-квантовая криптография), что защищает от будущих квантовых атак. Присутствие (presence) требует реального времени для создания подписей.
-
Sybil модель: В Montana Sybil-атаки влияют в основном на распределение наград, а не на безопасность сети, так как безопасность обеспечивается криптографически, а накопление присутствия требует реального времени.
Следствия для безопасности сетевого слоя:
- Защита от Eclipse критична, так как изоляция узла может привести к получению фейкового chain state
- DoS-атаки могут нарушить синхронизацию и доступность сети
- Memory exhaustion может привести к отказу в обслуживании
- Неправильная обработка сообщений может привести к некорректному состоянию узла
2. Изученные файлы
| Файл | LOC | Ключевые компоненты |
|---|---|---|
types.rs |
~400 | Константы сети, ограничения памяти, типы сообщений, state machines |
protocol.rs |
~1500 | Основной протокол P2P, обработка сообщений, handshake, connection loops |
addrman.rs |
~600 | Менеджер адресов с криптографическими бакетами, защита от отравления |
connection.rs |
~500 | Управление подключениями, ban list, retry logic, netgroup limits |
inventory.rs |
~600 | Управление инвентарем (slices, tx, presence), LRU eviction, relay cache |
peer.rs |
~400 | Состояние пира, rate limits, flow control, known inventory tracking |
message.rs |
~300 | Определение всех типов P2P сообщений, размеры, сериализация |
rate_limit.rs |
~400 | Token bucket rate limiting, flow control, adaptive subnet limiting |
bootstrap.rs |
~400 | Верификация bootstrap, Trusted Core модель, консенсус проверок |
eviction.rs |
~300 | Логика вытеснения пиров, многоуровневая защита |
sync.rs |
~700 | Headers-first sync, параллельная загрузка слайсов, orphan pool |
startup.rs |
~200 | Оркестрация полной верификации при старте |
subnet.rs |
~300 | Отслеживание репутации подсетей, защита от Eclipse |
verification.rs |
~700 | Клиент для верификации, аутентификация hardcoded нод |
hardcoded_identity.rs |
~200 | Определение hardcoded нод, ML-DSA-65 аутентификация |
discouraged.rs |
~300 | Rolling bloom filter для discouraged пиров |
feeler.rs |
~250 | Короткоживущие подключения для проверки адресов |
dns.rs |
~270 | DNS seeds и fallback IPs для bootstrap |
noise.rs |
~900 | Noise XX + ML-KEM-768 гибридное шифрование |
encrypted.rs |
~435 | Обертка для зашифрованных потоков, управление ключами |
mod.rs |
~75 | Объявления модулей и re-exports |
Всего: ~20 файлов, ~9000+ строк кода
3. Attack Surface
Точки входа для атакующего:
-
P2P подключения: Атакующий может инициировать входящие и исходящие TCP подключения, выполнять Noise handshake и отправлять P2P сообщения.
-
Bootstrap процесс: При старте узла атакующий может контролировать DNS seeds, fallback IPs (через BGP hijacking), или предоставлять фейковый chain state через P2P пиры.
-
Сообщения протокола: Атакующий может отправлять любые типы сообщений (Version, Inv, Addr, GetData, Slice, Tx, Presence и т.д.) в пределах rate limits.
-
Address management: Атакующий может отправлять адреса через
Addrсообщения для отравления адресной таблицы. -
Inventory flooding: Атакующий может отправлять множество уникальных inventory items для исчерпания памяти.
-
Handshake exhaustion: Атакующий может инициировать множество handshake без их завершения для исчерпания ресурсов.
4. Найденные уязвимости
[HIGH] Memory Exhaustion через неограниченный have_slice HashSet
Файл: inventory.rs:118
Уязвимый код:
pub struct Inventory {
// Items we have locally
// Slices use HashSet (bounded by chain length, not attacker-controlled)
have_slice: HashSet<Hash>,
// Tx and Presence use LruHashSet (attacker can flood with unique items)
have_tx: LruHashSet,
have_presence: LruHashSet,
// ...
}
Вектор атаки:
- Атакующий подключается к узлу и отправляет множество
Invсообщений с фейковыми slice hashes - Узел обрабатывает эти
Invи добавляет каждый hash вhave_sliceчерезadd_have()илиadd_slice() - Так как
have_sliceиспользует обычныйHashSetбез ограничений размера, память растет неограниченно - Комментарий предполагает, что slice hashes ограничены длиной цепи, но атакующий может отправлять фейковые hashes, которые не соответствуют реальным слайсам
Импакт:
- Memory exhaustion узла
- Возможный отказ в обслуживании (DoS)
- Узел может быть вынужден перезапуститься или упасть
Сложность:
- Низкая: требуется только одно подключение и отправка множества
Invсообщений - Ресурсы: минимальные (один бот или скрипт)
PoC сценарий:
# Псевдокод атаки
1. Подключиться к целевому узлу Montana
2. Выполнить Noise handshake
3. Отправить Version/Verack
4. Для i в range(1, 1000000):
5. Сгенерировать случайный hash h_i
6. Отправить Inv([InvItem(InvType::Slice, h_i)])
7. Узел добавит h_i в have_slice HashSet
8. Память узла исчерпается
Рекомендация: Использовать LruHashSet для have_slice с ограничением размера (например, MAX_SLICE_HAVE_ENTRIES = 1_000_000 для учета реальной длины цепи с запасом).
[MEDIUM] Resource Exhaustion через неограниченные handshake подключения
Файл: protocol.rs:589-613, connection.rs:186
Уязвимый код:
// protocol.rs:589
tokio::spawn(async move {
if let Err(e) = Self::handle_connection(
stream,
socket_addr,
false,
// ...
)
.await
{
// ...
}
});
// connection.rs:186
connecting: Mutex<HashSet<SocketAddr>>,
Вектор атаки:
- Атакующий инициирует множество TCP подключений к узлу одновременно (используя разные IP адреса)
- Каждое подключение добавляется в
connectingHashSet и создает новуюtokio::spawnзадачу - Атакующий начинает Noise handshake, но не завершает его (например, отправляет только часть сообщений или медленно отвечает)
- Каждое незавершенное handshake потребляет память (задача, буферы, криптографические операции) и CPU
connectingHashSet не имеет явного ограничения размера, толькоMAX_CONNECTIONS_PER_IPограничивает одно IP
Импакт:
- Исчерпание памяти через множество незавершенных handshake
- Исчерпание CPU через криптографические операции (ML-KEM-768, X25519)
- Возможный отказ в обслуживании
Сложность:
- Средняя: требуется множество IP адресов (например, ботнет или распределенная атака)
- Ресурсы: ботнет с ~1000+ узлов или распределенная атака
PoC сценарий:
# Псевдокод атаки
1. Получить доступ к ботнету с 1000+ IP адресов
2. Для каждого IP:
3. Инициировать TCP подключение к целевому узлу
4. Начать Noise handshake (отправить Message 0)
5. НЕ отвечать на Message 1 от узла (или отвечать очень медленно)
6. Поддержать подключение открытым до таймаута (30 секунд)
7. Повторить для всех IP
8. Узел будет иметь 1000+ незавершенных handshake, потребляющих ресурсы
Рекомендация:
- Добавить глобальное ограничение на количество одновременных подключений в процессе handshake (например,
MAX_CONCURRENT_HANDSHAKES = 100) - Добавить ограничение размера
connectingHashSet - Более агрессивные таймауты для handshake
[MEDIUM] CPU Exhaustion через дубликаты в Inv сообщениях
Файл: protocol.rs:1001-1022
Уязвимый код:
Message::Inv(items) => {
if items.len() > MAX_INV_SIZE {
return Err(NetError::Protocol("Too many inv items".into()));
}
if !peer.rate_limits.inv.try_consume(items.len()) {
debug!("Rate limited inv from {} ({} items)", peer.addr, items.len());
return Ok(false);
}
for item in &items {
peer.add_known_inv(item.hash);
}
let needed = inventory.read().await.filter_needed(&items);
if !needed.is_empty() {
for item in &needed {
inventory.write().await.request(item, peer.addr);
}
peer_tx.send(Message::GetData(needed)).await.ok();
}
}
Вектор атаки:
- Атакующий отправляет
Invсообщение сMAX_INV_SIZE(50,000) дубликатов одного и того же hash - Код не дедуплицирует элементы внутри одного сообщения перед обработкой
- Каждый элемент обрабатывается отдельно:
peer.add_known_inv(item.hash)вызывается 50,000 раз для одного hashinventory.read().await.filter_needed(&items)обрабатывает все 50,000 элементов- Хотя
BoundedInvSetвpeer.add_known_invиспользует HashSet и не добавит дубликаты, проверка выполняется 50,000 раз
- Это приводит к избыточному использованию CPU
Импакт:
- CPU exhaustion через избыточную обработку дубликатов
- Возможное замедление обработки легитимных сообщений
- DoS через ресурсное исчерпание
Сложность:
- Низкая: требуется только одно подключение
- Ресурсы: минимальные (один скрипт)
PoC сценарий:
# Псевдокод атаки
1. Подключиться к целевому узлу
2. Выполнить handshake
3. Сгенерировать один валидный hash h
4. Создать Inv сообщение с 50,000 копий InvItem(InvType::Slice, h)
5. Отправить это сообщение
6. Повторить шаги 3-5 с разными hashes
7. Узел будет обрабатывать миллионы дубликатов, потребляя CPU
Рекомендация: Дедуплицировать элементы внутри Inv сообщения перед обработкой:
let unique_items: Vec<InvItem> = items.iter()
.unique_by(|item| item.hash)
.cloned()
.collect();
[LOW] Потенциальная утечка памяти в ip_votes при частых переподключениях
Файл: protocol.rs:897, protocol.rs:834-839
Уязвимый код:
// При получении Version от outbound пира
ip_votes.write().await.insert(peer.addr, their_view_of_us);
// При отключении пира
peers.write().await.remove(&addr);
let _ = event_tx.send(NetEvent::PeerDisconnected(addr)).await;
if let Some(nonce) = our_sent_nonce {
sent_nonces.write().await.remove(&nonce);
}
// НО: ip_votes НЕ очищается при отключении пира
Вектор атаки:
- Атакующий создает множество outbound подключений к узлу
- Каждое подключение отправляет Version с "голосом" за внешний IP, добавляя запись в
ip_votes - Атакующий быстро отключается и переподключается с новыми адресами
- Старые записи в
ip_votesне удаляются при отключении - При частых переподключениях
ip_votesможет расти неограниченно
Импакт:
- Потенциальная утечка памяти (хотя комментарий говорит, что ограничено
MAX_OUTBOUND) - Если пиры часто переподключаются, старые записи накапливаются
Сложность:
- Средняя: требуется множество подключений и частые переподключения
- Ресурсы: ботнет или распределенная атака
PoC сценарий:
# Псевдокод атаки
1. Получить доступ к множеству IP адресов
2. Для каждого IP:
3. Подключиться к целевому узлу (outbound)
4. Отправить Version с "голосом" за фейковый IP
5. Немедленно отключиться
6. Повторить с новым IP
7. После 10,000 переподключений ip_votes будет содержать 10,000 записей
Рекомендация: Очищать ip_votes при отключении outbound пира:
if !peer.inbound {
ip_votes.write().await.remove(&addr);
}
[LOW] Потенциальная атака через false positives в DiscouragedFilter
Файл: discouraged.rs:13-105
Уязвимый код:
pub struct DiscouragedFilter {
data: Vec<u64>,
n_hash: u32,
n_elements: u32,
max_elements: u32,
generation: u32,
tweak: u64,
}
pub fn contains(&self, addr: &SocketAddr) -> bool {
// Bloom filter может иметь false positives
// ...
}
Вектор атаки:
- Атакующий изучает структуру bloom filter (хотя
tweakслучайный) - Атакующий создает множество адресов, которые могут вызвать false positives с легитимными адресами
- Легитимные пиры случайно попадают в discouraged filter
- Легитимные пиры деприоритизируются, что может помочь атакующему вытеснить их
Импакт:
- Деприоритизация легитимных пиров
- Помощь в Eclipse атаке (вытеснение хороших пиров)
Сложность:
- Высокая: требуется глубокое понимание структуры bloom filter и криптографических хешей
- Ресурсы: значительные вычислительные ресурсы для анализа
PoC сценарий:
# Псевдокод атаки (теоретический)
1. Изучить структуру DiscouragedFilter (SipHasher24, tweak)
2. Найти адреса, которые вызывают коллизии хешей с легитимными адресами
3. Добавить эти адреса в discouraged filter (через misbehavior)
4. Легитимные адреса могут попасть в false positive
5. Легитимные пиры деприоритизируются
Рекомендация: False positives в bloom filter приемлемы для "мягкого" наказания, но стоит мониторить частоту false positives и при необходимости увеличить размер фильтра или уменьшить false positive rate.
5. Атаки, которые НЕ работают
Классические блокчейн атаки:
-
51% атака: Не применима, так как нет майнинга или стейкинга. Безопасность обеспечивается криптографически и физическими ограничениями времени.
-
Selfish mining: Не применима, так как нет майнеров, которые могут удерживать блоки. Слайсы создаются детерминистически на основе хешей.
-
Long-range атака: Не применима, так как нельзя создать валидные координаты для прошлого времени (накопление присутствия требует реального времени).
-
Nothing-at-stake: Не применима, так как нет стейкинга и нет экономических стимулов для создания множества форков.
-
Eclipse через адресное отравление: Частично защищено криптографическими бакетами в AddrMan, hardcoded нодами, и netgroup diversity. Однако, если атакующий контролирует DNS/BGP, он может отравить bootstrap процесс.
-
Sybil атака на консенсус: Не применима в традиционном смысле, так как консенсус не зависит от количества нод. Однако Sybil может влиять на распределение наград и сетевую топологию.
Защитные механизмы, которые работают хорошо:
-
Rate limiting: Token bucket для различных типов сообщений эффективно ограничивает DoS через сообщения.
-
Flow control: Управление очередями предотвращает переполнение буферов.
-
Bounded collections: Большинство коллекций имеют явные ограничения размера (LruHashSet, MAX_RELAY_CACHE_ENTRIES, и т.д.).
-
Netgroup diversity: Ограничение подключений из одной /16 подсети защищает от Erebus-стиль атак.
-
Hardcoded nodes: ML-DSA-65 аутентификация hardcoded нод обеспечивает надежный bootstrap.
-
Adaptive subnet limiting: Двухуровневое адаптивное лимитирование подсетей защищает от распределенных атак.
6. Рекомендации
Критические исправления:
-
Исправить
have_sliceHashSet (inventory.rs:118):- Заменить на
LruHashSetс ограничением размера - Рекомендуемый размер:
MAX_SLICE_HAVE_ENTRIES = 1_000_000(для учета реальной длины цепи с запасом)
- Заменить на
-
Ограничить одновременные handshake (
protocol.rs:589,connection.rs:186):- Добавить глобальное ограничение
MAX_CONCURRENT_HANDSHAKES = 100 - Добавить ограничение размера
connectingHashSet - Более агрессивные таймауты для handshake
- Добавить глобальное ограничение
-
Дедуплицировать Inv сообщения (
protocol.rs:1001-1022):- Дедуплицировать элементы внутри
Invсообщения перед обработкой - Использовать
HashSetилиBTreeSetдля уникальности
- Дедуплицировать элементы внутри
Улучшения:
-
Очищать
ip_votesпри отключении (protocol.rs:834-839):- Добавить очистку
ip_votesпри отключении outbound пира
- Добавить очистку
-
Мониторинг false positives (
discouraged.rs):- Добавить метрики для отслеживания частоты false positives в DiscouragedFilter
- При необходимости увеличить размер фильтра или уменьшить false positive rate
-
Валидация содержимого сообщений:
- Добавить более строгую валидацию содержимого сообщений (например, проверка типов, диапазонов значений)
- Добавить метрики для отслеживания аномальных паттернов в сообщениях
7. Вердикт
- CRITICAL — есть уязвимости, позволяющие уничтожить сеть
- HIGH — есть серьёзные уязвимости
- MEDIUM — есть уязвимости среднего риска
- LOW — только minor issues
- SECURE — уязвимостей не найдено
Обоснование:
Найдены 4 уязвимости различной степени серьезности:
- HIGH: Memory exhaustion через неограниченный
have_sliceHashSet — может привести к DoS узла - MEDIUM: Resource exhaustion через неограниченные handshake — требует больше ресурсов, но все еще серьезная
- MEDIUM: CPU exhaustion через дубликаты в Inv — может замедлить обработку сообщений
- LOW: Потенциальная утечка памяти в
ip_votes— менее критична, но стоит исправить
Сетевой слой Montana в целом хорошо защищен с множеством защитных механизмов (rate limiting, bounded collections, netgroup diversity, hardcoded nodes). Однако найденные уязвимости показывают, что есть места для улучшения, особенно в области ограничения памяти и обработки дубликатов.
Общая оценка: Сетевой слой имеет хорошую архитектуру безопасности, но требует исправления найденных уязвимостей перед production deployment.