# Security Audit: Сетевой слой Montana **Модель:** Composer 1 **Компания:** Cursor **Дата:** 15.01.2025 20:00 UTC --- ## 1. Понимание архитектуры Montana использует принципиально иную модель консенсуса — Atemporal Coordinate Presence (ACP), основанную на физических ограничениях времени и вычислений, а не на традиционных механизмах блокчейна. ### Ключевые отличия от традиционных систем: 1. **Время как основа консенсуса**: Montana использует физические ограничения Слоя -1 (упорядоченность событий) для создания временных координат (τ₂), которые физически невозможно подделать или ускорить. Это означает, что атаки типа "long-range" или "nothing-at-stake" не применимы, так как нельзя создать валидные координаты для прошлого времени. 2. **Отсутствие майнеров/валидаторов**: Вместо этого используется детерминистическая лотерея на основе хешей для выбора "победителей" слайсов. Без майнинга или стейкинга нет экономических стимулов для атак типа 51% или selfish mining. 3. **Fork choice через время**: Выбор форка основан на временной цепочке (timechain), где каждый слайс ссылается на предыдущий через хеш. Это делает реорганизации предсказуемыми и ограниченными временем. 4. **Криптографическая безопасность**: Все подписи используют ML-DSA-65 (пост-квантовая криптография), что защищает от будущих квантовых атак. Присутствие (presence) требует реального времени для создания подписей. 5. **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 ### Точки входа для атакующего: 1. **P2P подключения**: Атакующий может инициировать входящие и исходящие TCP подключения, выполнять Noise handshake и отправлять P2P сообщения. 2. **Bootstrap процесс**: При старте узла атакующий может контролировать DNS seeds, fallback IPs (через BGP hijacking), или предоставлять фейковый chain state через P2P пиры. 3. **Сообщения протокола**: Атакующий может отправлять любые типы сообщений (Version, Inv, Addr, GetData, Slice, Tx, Presence и т.д.) в пределах rate limits. 4. **Address management**: Атакующий может отправлять адреса через `Addr` сообщения для отравления адресной таблицы. 5. **Inventory flooding**: Атакующий может отправлять множество уникальных inventory items для исчерпания памяти. 6. **Handshake exhaustion**: Атакующий может инициировать множество handshake без их завершения для исчерпания ресурсов. --- ## 4. Найденные уязвимости ### [HIGH] Memory Exhaustion через неограниченный `have_slice` HashSet **Файл:** `inventory.rs:118` **Уязвимый код:** ```rust pub struct Inventory { // Items we have locally // Slices use HashSet (bounded by chain length, not attacker-controlled) have_slice: HashSet, // Tx and Presence use LruHashSet (attacker can flood with unique items) have_tx: LruHashSet, have_presence: LruHashSet, // ... } ``` **Вектор атаки:** 1. Атакующий подключается к узлу и отправляет множество `Inv` сообщений с фейковыми slice hashes 2. Узел обрабатывает эти `Inv` и добавляет каждый hash в `have_slice` через `add_have()` или `add_slice()` 3. Так как `have_slice` использует обычный `HashSet` без ограничений размера, память растет неограниченно 4. Комментарий предполагает, что slice hashes ограничены длиной цепи, но атакующий может отправлять фейковые hashes, которые не соответствуют реальным слайсам **Импакт:** - Memory exhaustion узла - Возможный отказ в обслуживании (DoS) - Узел может быть вынужден перезапуститься или упасть **Сложность:** - Низкая: требуется только одно подключение и отправка множества `Inv` сообщений - Ресурсы: минимальные (один бот или скрипт) **PoC сценарий:** ```python # Псевдокод атаки 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` **Уязвимый код:** ```rust // protocol.rs:589 tokio::spawn(async move { if let Err(e) = Self::handle_connection( stream, socket_addr, false, // ... ) .await { // ... } }); // connection.rs:186 connecting: Mutex>, ``` **Вектор атаки:** 1. Атакующий инициирует множество TCP подключений к узлу одновременно (используя разные IP адреса) 2. Каждое подключение добавляется в `connecting` HashSet и создает новую `tokio::spawn` задачу 3. Атакующий начинает Noise handshake, но не завершает его (например, отправляет только часть сообщений или медленно отвечает) 4. Каждое незавершенное handshake потребляет память (задача, буферы, криптографические операции) и CPU 5. `connecting` HashSet не имеет явного ограничения размера, только `MAX_CONNECTIONS_PER_IP` ограничивает одно IP **Импакт:** - Исчерпание памяти через множество незавершенных handshake - Исчерпание CPU через криптографические операции (ML-KEM-768, X25519) - Возможный отказ в обслуживании **Сложность:** - Средняя: требуется множество IP адресов (например, ботнет или распределенная атака) - Ресурсы: ботнет с ~1000+ узлов или распределенная атака **PoC сценарий:** ```python # Псевдокод атаки 1. Получить доступ к ботнету с 1000+ IP адресов 2. Для каждого IP: 3. Инициировать TCP подключение к целевому узлу 4. Начать Noise handshake (отправить Message 0) 5. НЕ отвечать на Message 1 от узла (или отвечать очень медленно) 6. Поддержать подключение открытым до таймаута (30 секунд) 7. Повторить для всех IP 8. Узел будет иметь 1000+ незавершенных handshake, потребляющих ресурсы ``` **Рекомендация:** 1. Добавить глобальное ограничение на количество одновременных подключений в процессе handshake (например, `MAX_CONCURRENT_HANDSHAKES = 100`) 2. Добавить ограничение размера `connecting` HashSet 3. Более агрессивные таймауты для handshake --- ### [MEDIUM] CPU Exhaustion через дубликаты в Inv сообщениях **Файл:** `protocol.rs:1001-1022` **Уязвимый код:** ```rust 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(); } } ``` **Вектор атаки:** 1. Атакующий отправляет `Inv` сообщение с `MAX_INV_SIZE` (50,000) дубликатов одного и того же hash 2. Код не дедуплицирует элементы внутри одного сообщения перед обработкой 3. Каждый элемент обрабатывается отдельно: - `peer.add_known_inv(item.hash)` вызывается 50,000 раз для одного hash - `inventory.read().await.filter_needed(&items)` обрабатывает все 50,000 элементов - Хотя `BoundedInvSet` в `peer.add_known_inv` использует HashSet и не добавит дубликаты, проверка выполняется 50,000 раз 4. Это приводит к избыточному использованию CPU **Импакт:** - CPU exhaustion через избыточную обработку дубликатов - Возможное замедление обработки легитимных сообщений - DoS через ресурсное исчерпание **Сложность:** - Низкая: требуется только одно подключение - Ресурсы: минимальные (один скрипт) **PoC сценарий:** ```python # Псевдокод атаки 1. Подключиться к целевому узлу 2. Выполнить handshake 3. Сгенерировать один валидный hash h 4. Создать Inv сообщение с 50,000 копий InvItem(InvType::Slice, h) 5. Отправить это сообщение 6. Повторить шаги 3-5 с разными hashes 7. Узел будет обрабатывать миллионы дубликатов, потребляя CPU ``` **Рекомендация:** Дедуплицировать элементы внутри `Inv` сообщения перед обработкой: ```rust let unique_items: Vec = items.iter() .unique_by(|item| item.hash) .cloned() .collect(); ``` --- ### [LOW] Потенциальная утечка памяти в `ip_votes` при частых переподключениях **Файл:** `protocol.rs:897`, `protocol.rs:834-839` **Уязвимый код:** ```rust // При получении 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 НЕ очищается при отключении пира ``` **Вектор атаки:** 1. Атакующий создает множество outbound подключений к узлу 2. Каждое подключение отправляет Version с "голосом" за внешний IP, добавляя запись в `ip_votes` 3. Атакующий быстро отключается и переподключается с новыми адресами 4. Старые записи в `ip_votes` не удаляются при отключении 5. При частых переподключениях `ip_votes` может расти неограниченно **Импакт:** - Потенциальная утечка памяти (хотя комментарий говорит, что ограничено `MAX_OUTBOUND`) - Если пиры часто переподключаются, старые записи накапливаются **Сложность:** - Средняя: требуется множество подключений и частые переподключения - Ресурсы: ботнет или распределенная атака **PoC сценарий:** ```python # Псевдокод атаки 1. Получить доступ к множеству IP адресов 2. Для каждого IP: 3. Подключиться к целевому узлу (outbound) 4. Отправить Version с "голосом" за фейковый IP 5. Немедленно отключиться 6. Повторить с новым IP 7. После 10,000 переподключений ip_votes будет содержать 10,000 записей ``` **Рекомендация:** Очищать `ip_votes` при отключении outbound пира: ```rust if !peer.inbound { ip_votes.write().await.remove(&addr); } ``` --- ### [LOW] Потенциальная атака через false positives в DiscouragedFilter **Файл:** `discouraged.rs:13-105` **Уязвимый код:** ```rust pub struct DiscouragedFilter { data: Vec, n_hash: u32, n_elements: u32, max_elements: u32, generation: u32, tweak: u64, } pub fn contains(&self, addr: &SocketAddr) -> bool { // Bloom filter может иметь false positives // ... } ``` **Вектор атаки:** 1. Атакующий изучает структуру bloom filter (хотя `tweak` случайный) 2. Атакующий создает множество адресов, которые могут вызвать false positives с легитимными адресами 3. Легитимные пиры случайно попадают в discouraged filter 4. Легитимные пиры деприоритизируются, что может помочь атакующему вытеснить их **Импакт:** - Деприоритизация легитимных пиров - Помощь в Eclipse атаке (вытеснение хороших пиров) **Сложность:** - Высокая: требуется глубокое понимание структуры bloom filter и криптографических хешей - Ресурсы: значительные вычислительные ресурсы для анализа **PoC сценарий:** ```python # Псевдокод атаки (теоретический) 1. Изучить структуру DiscouragedFilter (SipHasher24, tweak) 2. Найти адреса, которые вызывают коллизии хешей с легитимными адресами 3. Добавить эти адреса в discouraged filter (через misbehavior) 4. Легитимные адреса могут попасть в false positive 5. Легитимные пиры деприоритизируются ``` **Рекомендация:** False positives в bloom filter приемлемы для "мягкого" наказания, но стоит мониторить частоту false positives и при необходимости увеличить размер фильтра или уменьшить false positive rate. --- ## 5. Атаки, которые НЕ работают ### Классические блокчейн атаки: 1. **51% атака**: Не применима, так как нет майнинга или стейкинга. Безопасность обеспечивается криптографически и физическими ограничениями времени. 2. **Selfish mining**: Не применима, так как нет майнеров, которые могут удерживать блоки. Слайсы создаются детерминистически на основе хешей. 3. **Long-range атака**: Не применима, так как нельзя создать валидные координаты для прошлого времени (накопление присутствия требует реального времени). 4. **Nothing-at-stake**: Не применима, так как нет стейкинга и нет экономических стимулов для создания множества форков. 5. **Eclipse через адресное отравление**: Частично защищено криптографическими бакетами в AddrMan, hardcoded нодами, и netgroup diversity. Однако, если атакующий контролирует DNS/BGP, он может отравить bootstrap процесс. 6. **Sybil атака на консенсус**: Не применима в традиционном смысле, так как консенсус не зависит от количества нод. Однако Sybil может влиять на распределение наград и сетевую топологию. ### Защитные механизмы, которые работают хорошо: 1. **Rate limiting**: Token bucket для различных типов сообщений эффективно ограничивает DoS через сообщения. 2. **Flow control**: Управление очередями предотвращает переполнение буферов. 3. **Bounded collections**: Большинство коллекций имеют явные ограничения размера (LruHashSet, MAX_RELAY_CACHE_ENTRIES, и т.д.). 4. **Netgroup diversity**: Ограничение подключений из одной /16 подсети защищает от Erebus-стиль атак. 5. **Hardcoded nodes**: ML-DSA-65 аутентификация hardcoded нод обеспечивает надежный bootstrap. 6. **Adaptive subnet limiting**: Двухуровневое адаптивное лимитирование подсетей защищает от распределенных атак. --- ## 6. Рекомендации ### Критические исправления: 1. **Исправить `have_slice` HashSet** (`inventory.rs:118`): - Заменить на `LruHashSet` с ограничением размера - Рекомендуемый размер: `MAX_SLICE_HAVE_ENTRIES = 1_000_000` (для учета реальной длины цепи с запасом) 2. **Ограничить одновременные handshake** (`protocol.rs:589`, `connection.rs:186`): - Добавить глобальное ограничение `MAX_CONCURRENT_HANDSHAKES = 100` - Добавить ограничение размера `connecting` HashSet - Более агрессивные таймауты для handshake 3. **Дедуплицировать Inv сообщения** (`protocol.rs:1001-1022`): - Дедуплицировать элементы внутри `Inv` сообщения перед обработкой - Использовать `HashSet` или `BTreeSet` для уникальности ### Улучшения: 4. **Очищать `ip_votes` при отключении** (`protocol.rs:834-839`): - Добавить очистку `ip_votes` при отключении outbound пира 5. **Мониторинг false positives** (`discouraged.rs`): - Добавить метрики для отслеживания частоты false positives в DiscouragedFilter - При необходимости увеличить размер фильтра или уменьшить false positive rate 6. **Валидация содержимого сообщений**: - Добавить более строгую валидацию содержимого сообщений (например, проверка типов, диапазонов значений) - Добавить метрики для отслеживания аномальных паттернов в сообщениях --- ## 7. Вердикт - [ ] CRITICAL — есть уязвимости, позволяющие уничтожить сеть - [x] HIGH — есть серьёзные уязвимости - [ ] MEDIUM — есть уязвимости среднего риска - [ ] LOW — только minor issues - [ ] SECURE — уязвимостей не найдено **Обоснование:** Найдены **4 уязвимости** различной степени серьезности: 1. **HIGH**: Memory exhaustion через неограниченный `have_slice` HashSet — может привести к DoS узла 2. **MEDIUM**: Resource exhaustion через неограниченные handshake — требует больше ресурсов, но все еще серьезная 3. **MEDIUM**: CPU exhaustion через дубликаты в Inv — может замедлить обработку сообщений 4. **LOW**: Потенциальная утечка памяти в `ip_votes` — менее критична, но стоит исправить Сетевой слой Montana в целом хорошо защищен с множеством защитных механизмов (rate limiting, bounded collections, netgroup diversity, hardcoded nodes). Однако найденные уязвимости показывают, что есть места для улучшения, особенно в области ограничения памяти и обработки дубликатов. **Общая оценка:** Сетевой слой имеет хорошую архитектуру безопасности, но требует исправления найденных уязвимостей перед production deployment.